/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use self file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const MLINDEX_BYTES_NUM_2: number = 2;
const MLINDEX_BYTES_NUM_3: number = 3;
const MLINDEX_BYTES_NUM_4: number = 4;
const MLINDEX_BYTES_NUM_03: number = 0.3;
const MLINDEX_BYTES_NUM_05: number = 0.5;
const MLINDEX_BYTES_NUM_06745: number = 0.6745;
const MLINDEX_BYTES_NUM_20: number = 2.0;
const MLINDEX_BYTES_NUM_100: number = 100;
const MLINDEX_BYTES_NUM_52: number = -52;
const MLINDEX_BYTES_NUM_0520: number = -52.0;
const MLINDEX_BYTES_NUM_1000: number = 1000;
const MLINDEX_BYTES_NUM_512: number = 512;
const MLINDEX_BYTES_NUM_10: number = 10;
const MLINDEX_BYTES_NUM_50: number = 50;
const MLINDEX_BYTES_NUM_75: number = 75;
const MLINDEX_BYTES_NUM_075: number = 0.75;
const MLINDEX_BYTES_NUM_04375: number = -0.4375;
const MLINDEX_BYTES_NUM_30: number = 30;
const MLINDEX_BYTES_NUM_0964: number = 0.964;
const MLINDEX_BYTES_NUM_002: number = -2;
const MLINDEX_BYTES_NUM_001: number = 0.01;
const MLINDEX_BYTES_NUM_5: number = 5;
const MLINDEX_BYTES_NUM_6: number = 6;
const MLINDEX_BYTES_NUM_020: number = 20;
const MLINDEX_BYTES_NUM_050: number = 5.0;
const MLINDEX_BYTES_NUM_40: number = 4.0;
const MLINDEX_BYTES_NUM_040: number = 40;
const NUM_STIME_CONST = 1000;
const NUM_TIME_LOOP_CONST = 60;
const NUM_TIME_LOOP_5 = 5;

class MlOptions {
  inPlace: boolean = true;
  max: number = 0;
  min: number = 0;
  from: number = 0;
  to: number = 0;
  numberOfPoints: number = 0;
  variant: String = '';
  computeLeftSingularVectors: boolean = false;
  computeRightSingularVectors: boolean = false;
  autoTranspose: boolean = false;
  hiddenLayers: number[] = [];
  iterations!: number;
  learningRate!: number;
  dicts?: NeuralNetworkDict;
  model?: string;
  assumeSymmetric?: boolean = false;
  inputSize?: number;
  outputSize?: number;
  regularization!: number;
  epsilon?: number;
  activation!: string;
  activationParam?: number;
  layers: MlOptions[] = [];
  w!: Matrix;
  b!: Matrix;
  defaultOptions: Map<string, boolean> = new Map();

  constructor() {
    this.defaultOptions.set('assumeSymmetric', false);
  }
}

// ml-stat array.js
class MlStatArrayCls {
  compareNumbers(a: number, b: number): number {
    return a - b;
  }
  sum(values: number[]): number {
    let sum = 0;
    for (let i = 0; i < values.length; i++) {
      sum += values[i];
    }
    return sum;
  }

  max(values: number[]): number {
    let max = values[0];
    let l = values.length;
    for (let i = 1; i < l; i++) {
      if (values[i] > max) {
        max = values[i];
      }
    }
    return max;
  }

  min(values: number[]): number {
    let min = values[0];
    let l = values.length;
    for (let i = 1; i < l; i++) {
      if (values[i] < min) {
        min = values[i];
      }
    }
    return min;
  }

  minMax(values: number[]): [number, number] {
    let min = values[0];
    let max = values[0];
    let l = values.length;
    for (let i = 1; i < l; i++) {
      if (values[i] < min) {
        min = values[i];
      }
      if (values[i] > max) {
        max = values[i];
      }
    }
    return [min, max];
  }

  arithmeticMean(values: number[]): number {
    let sum = 0;
    let l = values.length;
    for (let i = 0; i < l; i++) {
      sum += values[i];
    }
    return sum / l;
  }

  mean(values: number[]): number {
    return this.arithmeticMean(values);
  }

  geometricMean(values: number[]): number {
    let mul = 1;
    let l = values.length;
    for (let i = 0; i < l; i++) {
      mul *= values[i];
    }
    return Math.pow(mul, 1 / l);
  }

  logMean(values: number[]): number {
    let lnsum = 0;
    let l = values.length;
    for (let i = 0; i < l; i++) {
      lnsum += Math.log(values[i]);
    }
    return lnsum / l;
  }

  grandMean(means: number[], samples: number[]): number {
    let sum = 0;
    let n = 0;
    let l = means.length;
    for (let i = 0; i < l; i++) {
      sum += samples[i] * means[i];
      n += samples[i];
    }
    return sum / n;
  }

  truncatedMean(values: number[], percent: number, alreadySorted: boolean): number {
    let newAlreadySorted = alreadySorted;
    let newValues = values;
    if (alreadySorted === undefined) {
      newAlreadySorted = false;
    }
    if (!newAlreadySorted) {
      newValues = newValues.sort(this.compareNumbers);
    }
    let l = newValues.length;
    let k = Math.floor(l * percent);
    let sum = 0;
    for (let i = k; i < l - k; i++) {
      sum += newValues[i];
    }
    return sum / (l - MLINDEX_BYTES_NUM_2 * k);
  }

  harmonicMean(values: number[]): number {
    let sum = 0;
    let l = values.length;
    for (let i = 0; i < l; i++) {
      if (values[i] === 0) {
        throw new RangeError('value at index ' + i + 'is zero');
      }
      sum += 1 / values[i];
    }
    return l / sum;
  }

  contraHarmonicMean(values: number[]): number {
    let r1 = 0;
    let r2 = 0;
    let l = values.length;
    for (let i = 0; i < l; i++) {
      r1 += values[i] * values[i];
      r2 += values[i];
    }
    if (r2 < 0) {
      throw new RangeError('sum of values is negative');
    }
    return r1 / r2;
  }

  median(values: number[], alreadySorted: boolean): number {
    let newAlreadySorted = alreadySorted;
    if (alreadySorted === undefined) {
      newAlreadySorted = false;
    }
    let newValues = values;
    if (!newAlreadySorted) {
      newValues = newValues.sort(this.compareNumbers);
    }
    let l = newValues.length;
    let half = Math.floor(l / MLINDEX_BYTES_NUM_2);
    if (l % MLINDEX_BYTES_NUM_2 === 0) {
      return (newValues[half - 1] + newValues[half]) * MLINDEX_BYTES_NUM_05;
    } else {
      return newValues[half];
    }
  }

  variance(values: number[], unbiased?: boolean): number {
    let newUnbiased = unbiased;
    if (unbiased === undefined) {
      newUnbiased = true;
    }
    let theMean = this.mean(values);
    let theVariance = 0;
    let l = values.length;

    for (let i = 0; i < l; i++) {
      let x = values[i] - theMean;
      theVariance += x * x;
    }

    if (newUnbiased) {
      return theVariance / (l - 1);
    } else {
      return theVariance / l;
    }
  }

  standardDeviation(values: number[], unbiased?: boolean): number {
    return Math.sqrt(this.variance(values, unbiased));
  }

  standardError(values: number[]): number {
    return this.standardDeviation(values) / Math.sqrt(values.length);
  }

  robustMeanAndStdev(y: []): [number, number] {
    let mean = 0;
    let stdev = 0;
    let length = y.length;
    for (let i = 0; i < length; i++) {
      mean += y[i];
    }
    mean /= length;
    let averageDeviations: number[] = Array(length).fill(0);
    for (let i = 0; i < length; i++) {
      averageDeviations[i] = Math.abs(y[i] - mean);
    }
    averageDeviations.sort(this.compareNumbers);
    if (length % MLINDEX_BYTES_NUM_2 === 1) {
      stdev = averageDeviations[(length - 1) / MLINDEX_BYTES_NUM_2] / MLINDEX_BYTES_NUM_06745;
    } else {
      stdev =
        (MLINDEX_BYTES_NUM_05 * (averageDeviations[length / MLINDEX_BYTES_NUM_2] + averageDeviations[length / MLINDEX_BYTES_NUM_2 - 1])) /
        MLINDEX_BYTES_NUM_06745;
    }
    return [mean, stdev];
  }

  quartiles(values: number[], alreadySorted: boolean): [number, number, number] {
    let newAlreadySorted = alreadySorted;
    if (typeof alreadySorted === 'undefined') {
      newAlreadySorted = false;
    }
    let newValues = values;
    if (!newAlreadySorted) {
      newValues = newValues.sort(this.compareNumbers);
    }
    let quart = newValues.length / MLINDEX_BYTES_NUM_4;
    let q1 = newValues[Math.ceil(quart) - 1];
    let q2 = this.median(newValues, true);
    let q3 = newValues[Math.ceil(quart * MLINDEX_BYTES_NUM_3) - 1];
    return [q1, q2, q3];
  }

  pooledStandardDeviation(samples: [], unbiased: boolean): number {
    return Math.sqrt(this.pooledVariance(samples, unbiased));
  }

  pooledVariance(samples: number[][], unbiased: boolean): number {
    let newUnbiased = unbiased;
    if (typeof unbiased === 'undefined') {
      newUnbiased = true;
    }
    let sum = 0;
    let count = 0;
    let l = samples.length;
    for (let i = 0; i < l; i++) {
      let values = samples[i];
      let vari = this.variance(values);
      sum += (values.length - 1) * vari;
      if (newUnbiased) {
        count += values.length - 1;
      } else {
        count += values.length;
      }
    }
    return sum / count;
  }

  mode(values: number[]): number {
    let l = values.length;
    let itemCount: number[] = Array(l).fill(0);

    for (let i = 0; i < l; i++) {
      itemCount[i] = 0;
    }
    let itemArray: number[] = Array(l).fill(0);
    let count = 0;
    for (let i = 0; i < l; i++) {
      let index = itemArray.indexOf(values[i]);
      if (index >= 0) {
        itemCount[index] += 1;
      } else {
        itemArray[count] = values[i];
        itemCount[count] = 1;
        count += 1;
      }
    }

    let maxValue: number = 0;
    let maxIndex: number = 0;
    for (let i = 0; i < count; i++) {
      if (itemCount[i] > maxValue) {
        maxValue = itemCount[i];
        maxIndex = i;
      }
    }
    return itemArray[maxIndex];
  }

  covariance(vector1: [], vector2: [], unbiased: boolean): number {
    let newUnbiased = unbiased;
    if (typeof unbiased === 'undefined') {
      newUnbiased = true;
    }
    let mean1 = this.mean(vector1);
    let mean2 = this.mean(vector2);
    if (vector1.length !== vector2.length) {
      throw new RangeError('Vectors do not have the same dimensions');
    }
    let cov = 0;
    let l = vector1.length;
    for (let i = 0; i < l; i++) {
      let x = vector1[i] - mean1;
      let y = vector2[i] - mean2;
      cov += x * y;
    }
    if (newUnbiased) {
      return cov / (l - 1);
    } else {
      return cov / l;
    }
  }

  skewness(values: number[], unbiased: boolean): number {
    let newUnbiased = unbiased;
    if (typeof unbiased === 'undefined') {
      newUnbiased = true;
    }
    let theMean = this.mean(values);
    let s2 = 0;
    let s3 = 0;
    let l = values.length;
    for (let i = 0; i < l; i++) {
      let dev = values[i] - theMean;
      s2 += dev * dev;
      s3 += dev * dev * dev;
    }
    let m2 = s2 / l;
    let m3 = s3 / l;
    let g = m3 / Math.pow(m2, MLINDEX_BYTES_NUM_3 / MLINDEX_BYTES_NUM_20);
    if (newUnbiased) {
      let a = Math.sqrt(l * (l - 1));
      let b = l - MLINDEX_BYTES_NUM_2;
      return (a / b) * g;
    } else {
      return g;
    }
  }

  kurtosis(values: number[], unbiased: boolean): number {
    let newUnbiased = unbiased;
    if (typeof unbiased === 'undefined') {
      newUnbiased = true;
    }
    let theMean = this.mean(values);
    let n = values.length;
    let s2 = 0;
    let s4 = 0;
    for (let i = 0; i < n; i++) {
      let dev = values[i] - theMean;
      s2 += dev * dev;
      s4 += dev * dev * dev * dev;
    }
    let m2 = s2 / n;
    let m4 = s4 / n;
    if (newUnbiased) {
      let v = s2 / (n - 1);
      let a = (n * (n + 1)) / ((n - 1) * (n - MLINDEX_BYTES_NUM_2) * (n - MLINDEX_BYTES_NUM_3));
      let b = s4 / (v * v);
      let c = ((n - 1) * (n - 1)) / ((n - MLINDEX_BYTES_NUM_2) * (n - MLINDEX_BYTES_NUM_3));
      return a * b - MLINDEX_BYTES_NUM_3 * c;
    } else {
      return m4 / (m2 * m2) - MLINDEX_BYTES_NUM_3;
    }
  }

  entropy(values: number[], eps: number): number {
    let newEps = eps;
    if (typeof eps === 'undefined') {
      newEps = 0;
    }
    let sum = 0;
    let l = values.length;
    for (let i = 0; i < l; i++) {
      sum += values[i] * Math.log(values[i] + newEps);
    }
    return -sum;
  }

  weightedMean(values: number[], weights: number[]): number {
    let sum = 0;
    let l = values.length;
    for (let i = 0; i < l; i++) {
      sum += values[i] * weights[i];
    }
    return sum;
  }

  weightedStandardDeviation(values: number[], weights: number[]): number {
    return Math.sqrt(this.weightedVariance(values, weights));
  }

  weightedVariance(values: number[], weights: number[]): number {
    let theMean = this.weightedMean(values, weights);
    let vari = 0;
    let l = values.length;
    let a = 0;
    let b = 0;
    for (let i = 0; i < l; i++) {
      let z = values[i] - theMean;
      let w = weights[i];
      vari += w * (z * z);
      b += w;
      a += w * w;
    }
    return vari * (b / (b * b - a));
  }

  center(values: number[], inPlace: boolean): void {
    let newInPlace = inPlace;
    if (typeof inPlace === 'undefined') {
      newInPlace = false;
    }
    let result = values;
    if (!newInPlace) {
      result = values;
    }
    let theMean = this.mean(result);
    let l = result.length;
    for (let i = 0; i < l; i++) {
      result[i] -= theMean;
    }
  }

  standardize(values: number[], standardDev: number, inPlace: boolean): number[] {
    let newStandardDev = standardDev;
    let newInPlace = inPlace;
    if (typeof standardDev === 'undefined') {
      newStandardDev = this.standardDeviation(values);
    }
    if (typeof inPlace === 'undefined') {
      newInPlace = false;
    }
    let l = values.length;
    let result: number[] = newInPlace ? values : new Array(l);
    for (let i = 0; i < l; i++) {
      result[i] = values[i] / newStandardDev;
    }
    return result;
  }

  cumulativeSum(array: number[]): number[] {
    let l = array.length;
    let result: number[] = Array(l).fill(0);
    result[0] = array[0];
    for (let i = 1; i < l; i++) {
      result[i] = result[i - 1] + array[i];
    }
    return result;
  }
}

let mlStatArray = new MlStatArrayCls();

class MlStatMatrixCls {
  compareNumbers(a: number, b: number): number {
    return a - b;
  }
  max(matrix: number[][]): number {
    let max: number = Number.NEGATIVE_INFINITY;
    for (let i = 0; i < matrix.length; i++) {
      for (let j = 0; j < matrix[i].length; j++) {
        if (matrix[i][j] > max) {
          max = matrix[i][j];
        }
      }
    }
    return max;
  }

  min(matrix: number[][]): number {
    let min = Number.POSITIVE_INFINITY;
    for (let i = 0; i < matrix.length; i++) {
      for (let j = 0; j < matrix[i].length; j++) {
        if (matrix[i][j] < min) {
          min = matrix[i][j];
        }
      }
    }
    return min;
  }

  minMax(matrix: number[][]): [number, number] {
    let min = Number.POSITIVE_INFINITY;
    let max = Number.NEGATIVE_INFINITY;
    for (let i = 0; i < matrix.length; i++) {
      for (let j = 0; j < matrix[i].length; j++) {
        if (matrix[i][j] < min) {
          min = matrix[i][j];
        }
        if (matrix[i][j] > max) {
          max = matrix[i][j];
        }
      }
    }
    return [min, max];
  }

  entropy(matrix: number[][], eps: number): number {
    let newEps = eps;
    if (typeof eps === 'undefined') {
      newEps = 0;
    }
    let sum = 0;
    let l1 = matrix.length;
    let l2 = matrix[0].length;
    for (let i = 0; i < l1; i++) {
      for (let j = 0; j < l2; j++) {
        sum += matrix[i][j] * Math.log(matrix[i][j] + newEps!);
      }
    }
    return -sum;
  }

  mean(matrix: number[][], dimension?: number): number[] {
    let newDimension = dimension;
    if (typeof dimension === 'undefined') {
      newDimension = 0;
    }
    let rows = matrix.length;
    let cols = matrix[0].length;
    let theMean: number[];
    let n: number;

    if (dimension === -1) {
      theMean = [0];
      n = rows * cols;
      for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
          theMean[0] += matrix[i][j];
        }
      }
      theMean[0] /= n;
    } else if (newDimension === 0) {
      theMean = Array(cols).fill(0);
      n = rows;
      for (let j = 0; j < cols; j++) {
        theMean[j] = 0;
        for (let i = 0; i < rows; i++) {
          theMean[j] += matrix[i][j];
        }
        theMean[j] /= n;
      }
    } else if (newDimension === 1) {
      theMean = Array(rows).fill(0);
      n = cols;
      for (let j = 0; j < rows; j++) {
        theMean[j] = 0;
        for (let i = 0; i < cols; i++) {
          theMean[j] += matrix[j][i];
        }
        theMean[j] /= n;
      }
    } else {
      throw new Error('Invalid dimension');
    }
    return theMean;
  }

  sum(matrix: number[][], dimension: number): number[] {
    let newDimension = dimension;
    if (typeof dimension === 'undefined') {
      newDimension = 0;
    }
    let rows = matrix.length;
    let cols = matrix[0].length;
    let theSum: number[];

    if (newDimension === -1) {
      theSum = [0];
      for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
          theSum[0] += matrix[i][j];
        }
      }
    } else if (newDimension === 0) {
      theSum = Array(cols).fill(0);
      for (let j = 0; j < cols; j++) {
        theSum[j] = 0;
        for (let i = 0; i < rows; i++) {
          theSum[j] += matrix[i][j];
        }
      }
    } else if (newDimension === 1) {
      theSum = Array(rows).fill(0);
      for (let j = 0; j < rows; j++) {
        theSum[j] = 0;
        for (let i = 0; i < cols; i++) {
          theSum[j] += matrix[j][i];
        }
      }
    } else {
      throw new Error('Invalid dimension');
    }
    return theSum;
  }

  product(matrix: number[][], dimension?: number): number[] {
    let newDimension = dimension;
    if (dimension === undefined) {
      newDimension = 0;
    }
    let rows = matrix.length;
    let cols = matrix[0].length;
    let theProduct: number[];

    if (newDimension === -1) {
      theProduct = [1];
      for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
          theProduct[0] *= matrix[i][j];
        }
      }
    } else if (newDimension === 0) {
      theProduct = Array(cols).fill(0);
      for (let j = 0; j < cols; j++) {
        theProduct[j] = 1;
        for (let i = 0; i < rows; i++) {
          theProduct[j] *= matrix[i][j];
        }
      }
    } else if (newDimension === 1) {
      theProduct = Array(rows).fill(0);
      for (let j = 0; j < rows; j++) {
        theProduct[j] = 1;
        for (let i = 0; i < cols; i++) {
          theProduct[j] *= matrix[j][i];
        }
      }
    } else {
      throw new Error('Invalid dimension');
    }
    return theProduct;
  }

  standardDeviation(matrix: number[][], means?: number[], unbiased?: boolean): number[] {
    let vari: number[] = this.variance(matrix, means!, unbiased!);
    let l = vari.length;
    for (let i = 0; i < l; i++) {
      vari[i] = Math.sqrt(vari[i]);
    }
    return vari;
  }

  variance(matrix: number[][], means: number[], unbiased: boolean): number[] {
    let newUnbiased = unbiased;
    if (typeof unbiased === 'undefined') {
      newUnbiased = true;
    }
    let newMeans = means;
    if (typeof newMeans === 'undefined') {
      newMeans = this.mean(matrix);
    }

    let rows = matrix.length;
    if (rows === 0) {
      return new Array();
    }
    let cols = matrix[0].length;
    let vari: number[] = Array(cols).fill(0.0);

    for (let j = 0; j < cols; j++) {
      let sum1 = 0;
      let sum2 = 0;
      let x = 0;
      for (let i = 0; i < rows; i++) {
        x = matrix[i][j] - newMeans[j];
        sum1 += x;
        sum2 += x * x;
      }
      if (newUnbiased) {
        vari[j] = (sum2 - (sum1 * sum1) / rows) / (rows - 1);
      } else {
        vari[j] = (sum2 - (sum1 * sum1) / rows) / rows;
      }
    }
    return vari;
  }

  median(matrix: number[][]): number[] {
    let rows = matrix.length;
    let cols = matrix[0].length;
    let medians: number[] = Array(cols).fill(0);
    for (let i = 0; i < cols; i++) {
      let data: number[] = Array(rows).fill(0);
      for (let j = 0; j < rows; j++) {
        data[j] = matrix[j][i];
      }
      data.sort(this.compareNumbers);
      let n = data.length;
      if (n % MLINDEX_BYTES_NUM_2 === 0) {
        medians[i] = (data[n / MLINDEX_BYTES_NUM_2] + data[n / MLINDEX_BYTES_NUM_2 - 1]) * MLINDEX_BYTES_NUM_05;
      } else {
        medians[i] = data[Math.floor(n / MLINDEX_BYTES_NUM_2)];
      }
    }
    return medians;
  }

  mode(matrix: number[][]): number[] {
    let rows = matrix.length;
    let cols = matrix[0].length;
    let modes: number[] = Array(cols).fill(0);

    for (let i = 0; i < cols; i++) {
      let itemCount: number[] = Array(rows).fill(0);
      for (let k = 0; k < rows; k++) {
        itemCount[k] = 0;
      }
      let itemArray: number[] = Array(rows).fill(0);
      let count = 0;
      for (let j = 0; j < rows; j++) {
        let index = itemArray.indexOf(matrix[j][i]);
        if (index >= 0) {
          itemCount[index] += 1;
        } else {
          itemArray[count] = matrix[j][i];
          itemCount[count] = 1;
          count += 1;
        }
      }

      let maxValue = 0;
      let maxIndex = 0;
      for (let j = 0; j < count; j++) {
        if (itemCount[j] > maxValue) {
          maxValue = itemCount[j];
          maxIndex = j;
        }
      }
      modes[i] = itemArray[maxIndex];
    }
    return modes;
  }

  skewness(matrix: number[][], unbiased?: boolean): number[] {
    let newUnbiased = unbiased;
    if (typeof unbiased === 'undefined') {
      newUnbiased = true;
    }
    let means = this.mean(matrix);
    let n = matrix.length;
    let l = means.length;
    let skew: number[] = new Array(l).fill(0.0);
    for (let j = 0; j < l; j++) {
      let s2 = 0;
      let s3 = 0;
      for (let i = 0; i < n; i++) {
        let dev = matrix[i][j] - means[j];
        s2 += dev * dev;
        s3 += dev * dev * dev;
      }

      let m2 = s2 / n;
      let m3 = s3 / n;
      let g = m3 / Math.pow(m2, MLINDEX_BYTES_NUM_3 / MLINDEX_BYTES_NUM_2);

      if (newUnbiased) {
        let a = Math.sqrt(n * (n - 1));
        let b = n - MLINDEX_BYTES_NUM_2;
        skew[j] = (a / b) * g;
      } else {
        skew[j] = g;
      }
    }
    return skew;
  }

  kurtosis(matrix: number[][], unbiased: boolean): number[] {
    let newUnbiased = unbiased;
    if (typeof unbiased === 'undefined') {
      newUnbiased = true;
    }
    let means = this.mean(matrix);
    let n = matrix.length;
    let m = matrix[0].length;
    let kurt: number[] = new Array(m).fill(0);
    for (let j = 0; j < m; j++) {
      let s2 = 0;
      let s4 = 0;
      for (let i = 0; i < n; i++) {
        let dev = matrix[i][j] - means[j];
        s2 += dev * dev;
        s4 += dev * dev * dev * dev;
      }
      let m2 = s2 / n;
      let m4 = s4 / n;

      if (newUnbiased) {
        let v = s2 / (n - 1);
        let a = (n * (n + 1)) / ((n - 1) * (n - MLINDEX_BYTES_NUM_2) * (n - MLINDEX_BYTES_NUM_3));
        let b = s4 / (v * v);
        let c = ((n - 1) * (n - 1)) / ((n - MLINDEX_BYTES_NUM_2) * (n - MLINDEX_BYTES_NUM_3));
        kurt[j] = a * b - MLINDEX_BYTES_NUM_3 * c;
      } else {
        kurt[j] = m4 / (m2 * m2) - MLINDEX_BYTES_NUM_3;
      }
    }
    return kurt;
  }

  standardError(matrix: number[][]): number[] {
    let samples: number = matrix.length;
    let standardDeviations: number[] = this.standardDeviation(matrix);
    let l: number = standardDeviations.length;
    let standardErrors: number[] = Array(l).fill(0);
    let sqrtN: number = Math.sqrt(samples);

    for (let i = 0; i < l; i++) {
      standardErrors[i] = standardDeviations[i] / sqrtN;
    }
    return standardErrors;
  }

  covariance(matrix: number[][], dimension?: number): number[][] {
    return this.scatter(matrix, undefined, dimension!);
  }

  scatter(matrix: number[][], divisor?: number, dimension?: number): number[][] {
    let newDimension = dimension;
    let newDivisor = divisor;
    if (typeof dimension === 'undefined') {
      newDimension = 0;
    }
    if (typeof divisor === 'undefined') {
      if (newDimension === 0) {
        newDivisor = matrix.length - 1;
      } else if (dimension === 1) {
        newDivisor = matrix[0].length - 1;
      }
    }
    let means = this.mean(matrix, dimension);
    let rows = matrix.length;
    if (rows === 0) {
      return [[]];
    }
    let cols: number = matrix[0].length;
    let cov: number[][];
    let s: number;

    if (newDimension === 0) {
      cov = Array(cols).fill([0]);
      for (let i = 0; i < cols; i++) {
        cov[i] = Array(cols).fill(0);
      }
      for (let i = 0; i < cols; i++) {
        for (let j = i; j < cols; j++) {
          s = 0;
          for (let k = 0; k < rows; k++) {
            s += (matrix[k][j] - means[j]) * (matrix[k][i] - means[i]);
          }
          s /= Number(newDivisor);
          cov[i][j] = s;
          cov[j][i] = s;
        }
      }
    } else if (newDimension === 1) {
      cov = Array(rows).fill([0]);
      for (let i = 0; i < rows; i++) {
        cov[i] = Array(rows).fill(0);
      }
      for (let i = 0; i < rows; i++) {
        for (let j = i; j < rows; j++) {
          s = 0;
          for (let k = 0; k < cols; k++) {
            s += (matrix[j][k] - means[j]) * (matrix[i][k] - means[i]);
          }
          s /= Number(newDivisor);
          cov[i][j] = s;
          cov[j][i] = s;
        }
      }
    } else {
      throw new Error('Invalid dimension');
    }
    return cov;
  }

  correlation(matrix: number[][]): number[][] {
    let means = this.mean(matrix);
    let standardDeviations = this.standardDeviation(matrix, means, true);
    let scores = this.zScores(matrix, means, standardDeviations);
    let rows = matrix.length;
    let cols: number = matrix[0].length;
    let cor: number[][] = Array(cols).fill([0]);

    for (let i = 0; i < cols; i++) {
      cor[i] = Array(cols).fill(0);
    }
    for (let i = 0; i < cols; i++) {
      for (let j = i; j < cols; j++) {
        let c = 0;
        for (let k = 0; k < scores.length; k++) {
          c += scores[k][j] * scores[k][i];
        }
        c /= rows - 1;
        cor[i][j] = c;
        cor[j][i] = c;
      }
    }
    return cor;
  }

  zScores(matrix: number[][], means: number[], standardDeviations: number[]): number[][] {
    let newStandardDeviations = standardDeviations;
    let newMeans = means;
    if (typeof means === 'undefined') {
      newMeans = this.mean(matrix);
    }
    if (typeof standardDeviations === 'undefined') {
      newStandardDeviations = this.standardDeviation(matrix, newMeans, true);
    }
    return this.standardize(this.center(matrix, newMeans, false), newStandardDeviations, true);
  }

  center(matrix: number[][], means: number[], inPlace: boolean): number[][] {
    let newMeans = means;
    if (typeof means === 'undefined') {
      newMeans = this.mean(matrix);
    }

    let result = matrix;
    let l = matrix.length;

    if (!inPlace) {
      result = new Array(l).fill([0]);
      for (let i = 0; i < l; i++) {
        result[i] = Array(matrix[i].length).fill(0);
      }
    }

    for (let i = 0; i < l; i++) {
      let row = result[i];
      for (let j = 0; j < row.length; j++) {
        row[j] = matrix[i][j] - newMeans[j];
      }
    }
    return result;
  }

  standardize(matrix: number[][], standardDeviations: number[], inPlace: boolean): number[][] {
    let newStandardDeviations = standardDeviations;
    if (typeof standardDeviations === 'undefined') {
      newStandardDeviations = this.standardDeviation(matrix);
    }
    let result = matrix;
    let l = matrix.length;

    if (!inPlace) {
      result = new Array(l).fill([0]);
      for (let i = 0; i < l; i++) {
        result[i] = Array(matrix[i].length).fill(0);
      }
    }

    for (let i = 0; i < l; i++) {
      let resultRow = result[i];
      let sourceRow = matrix[i];
      for (let j = 0; j < resultRow.length; j++) {
        if (standardDeviations[j] !== 0) {
          resultRow[j] = sourceRow[j] / newStandardDeviations[j];
        }
      }
    }
    return result;
  }

  weightedVariance(matrix: number[][], weights: number[]): number[] {
    let means = this.mean(matrix);
    let rows = matrix.length;
    if (rows === 0) {
      return [];
    }
    let cols = matrix[0].length;
    let vari: number[] = Array(cols).fill(0);

    for (let j = 0; j < cols; j++) {
      let sum = 0;
      let a = 0;
      let b = 0;

      for (let i = 0; i < rows; i++) {
        let z = matrix[i][j] - means[j];
        let w = weights[i];

        sum += w * (z * z);
        b += w;
        a += w * w;
      }

      vari[j] = sum * (b / (b * b - a));
    }

    return vari;
  }

  weightedMean(matrix: number[][], weights: number[], dimension?: number): number[] {
    let newDimension = dimension;
    if (typeof dimension === 'undefined') {
      newDimension = 0;
    }
    let rows = matrix.length;
    if (rows === 0) {
      return [];
    }
    let cols: number = matrix[0].length;
    let means: number[];
    let w: number;
    let row: number[];

    if (newDimension === 0) {
      means = Array(cols).fill(0);
      for (let i = 0; i < cols; i++) {
        means[i] = 0;
      }
      for (let i = 0; i < rows; i++) {
        row = matrix[i];
        w = weights[i];
        for (let j = 0; j < cols; j++) {
          means[j] += row[j] * w;
        }
      }
    } else if (newDimension === 1) {
      means = Array(rows).fill(0);
      for (let i = 0; i < rows; i++) {
        means[i] = 0;
      }
      for (let j = 0; j < rows; j++) {
        row = matrix[j];
        w = weights[j];
        for (let i = 0; i < cols; i++) {
          means[j] += row[i] * w;
        }
      }
    } else {
      throw new Error('Invalid dimension');
    }

    let weightSum: number = mlStatArray.sum(weights);
    if (weightSum !== 0) {
      for (let i = 0; i < means.length; i++) {
        means[i] /= weightSum;
      }
    }
    return means;
  }

  weightedCovariance(matrix: number[][], weights: number[], means: number[], dimension: number): number[] | number[][] {
    let newMeans = means;
    let newDimension = dimension;
    if (typeof dimension === 'undefined') {
      newDimension = 0;
    }
    if (typeof newMeans === 'undefined') {
      newMeans = this.weightedMean(matrix, weights, dimension);
    }
    let s1 = 0;
    let s2 = 0;
    for (let i = 0; i < weights.length; i++) {
      s1 += weights[i];
      s2 += weights[i] * weights[i];
    }
    let factor = s1 / (s1 * s1 - s2);
    return this.weightedScatter(matrix, weights, newMeans, factor, newDimension);
  }

  weightedScatter(matrix: number[][], weights: number[], means: number[], factor: number, dimension: number): number[] | number[][] {
    let newDimension = dimension;
    let newMeans = means;
    let newFactor = factor;
    if (typeof newDimension === 'undefined') {
      newDimension = 0;
    }
    if (typeof newMeans === 'undefined') {
      newMeans = this.weightedMean(matrix, weights, newDimension);
    }
    if (typeof factor === 'undefined') {
      newFactor = 1;
    }
    let rows = matrix.length;
    if (rows === 0) {
      return [[]];
    }
    let cols = matrix[0].length;
    let cov: number[][] | number;
    let s: number;

    if (newDimension === 0) {
      cov = Array(cols).fill(0);
      for (let i = 0; i < cols; i++) {
        cov[i] = Array(cols).fill(0);
      }
      for (let i = 0; i < cols; i++) {
        for (let j = i; j < cols; j++) {
          s = 0;
          for (let k = 0; k < rows; k++) {
            s += weights[k] * (matrix[k][j] - means[j]) * (matrix[k][i] - means[i]);
          }
          cov[i][j] = s * factor;
          cov[j][i] = s * factor;
        }
      }
    } else if (newDimension === 1) {
      cov = Array(rows).fill([0]);
      for (let i = 0; i < rows; i++) {
        cov[i] = Array(rows).fill(0);
      }
      for (let i = 0; i < rows; i++) {
        for (let j = i; j < rows; j++) {
          s = 0;
          for (let k = 0; k < cols; k++) {
            s += weights[k] * (matrix[j][k] - means[j]) * (matrix[i][k] - means[i]);
          }
          cov[i][j] = s * newFactor;
          cov[j][i] = s * newFactor;
        }
      }
    } else {
      throw new Error('Invalid dimension');
    }
    return cov;
  }
}

let mlStatMatrix = new MlStatMatrixCls();

// ml-array-utils ArrayUtils.js
class MlArrayUtilsCls {
  getEquallySpacedData(x: number[], y: number[], options?: MlOptions): number[] {
    return mlArrayUtilsGetEquallySpaced.getEquallySpacedData(x, y, options as MlOptions);
  }

  snv(data: number[]): number[] {
    let mean: number = mlStatArray.mean(data);
    let std: number = mlStatArray.standardDeviation(data);
    let result = [...data];
    for (let i = 0; i < data.length; i++) {
      result[i] = (result[i] - mean) / std;
    }
    return result;
  }
}

let mlArrayUtils = new MlArrayUtilsCls();

// ml-array-utils getEquallySpaced.js

class MlArrayUtilsGetEquallySpacedCls {
  getEquallySpacedData(x: number[], y: number[], options: MlOptions): number[] {
    let newOptions = options;
    let newX = x;
    let newY = y;

    if (x.length > 1 && x[0] > x[1]) {
      newX = [...x.reverse()];
      newY = [...y.reverse()];
    }

    let xLength = x.length;
    if (xLength !== y.length) {
      throw new RangeError("the x and y vector does't have the same size.");
    }
    let from: number;
    if (options === undefined) {
      newOptions = new MlOptions();
    }
    if (newOptions?.from === 0) {
      from = Number(x[0]);
    } else {
      from = newOptions!.from;
    }

    let to: number = newOptions.to === undefined ? newX[newX.length - 1] : newOptions.to;

    let reverse: boolean = from > to;
    if (reverse) {
      let temp = from;
      from = to;
      to = temp;
    }
    let numberOfPoints: number = newOptions.numberOfPoints === undefined ? MLINDEX_BYTES_NUM_100 : newOptions.numberOfPoints;
    let algorithm = newOptions.variant === 'slot' ? 'slot' : 'smooth'; // default value: smooth
    let output = algorithm === 'slot' ? 
      this.getEquallySpacedSlot(newX, newY, Number(from), Number(to), numberOfPoints) : 
      this.getEquallySpacedSmooth(newX, newY, Number(from), Number(to), numberOfPoints);
    if (reverse) {
      return output.reverse();
    } else {
      return output;
    }
  }

  getSlope(x0: number, y0: number, x1: number, y1: number): number {
    return (y1 - y0) / (x1 - x0);
  }

  getEquallySpacedSmooth(x: number[], y: number[], from: number, to: number, numberOfPoints?: number): number[] {
    let xLength = x.length;
    let step = (to - from) / Number(numberOfPoints! - 1);
    let halfStep = step / MLINDEX_BYTES_NUM_2;

    let start = from - halfStep;
    let output: number[] = Array(numberOfPoints).fill(0);

    let initialOriginalStep = x[1] - x[0];
    let lastOriginalStep = x[x.length - 1] - x[x.length - MLINDEX_BYTES_NUM_2];

    let min = start;
    let max = start + step;

    let previousX = Number.MIN_VALUE;
    let previousY = 0;
    let nextX = x[0] - initialOriginalStep;
    let nextY = 0;

    let currentValue = 0;
    let slope = 0;
    let intercept = 0;
    let sumAtMin = 0;
    let sumAtMax = 0;

    let i = 0;
    let j = 0;
    let mainWhile = true;
    main: while (mainWhile) {
      while (nextX - Number(max) >= 0) {
        // no overlap with original point, just consume current value
        let add = this.integral(0, Number(Number(max) - previousX), Number(slope), Number(previousY));
        sumAtMax = currentValue + add;

        output[j] = (sumAtMax - sumAtMin) / Number(step);
        j += 1;

        if (j === numberOfPoints) {
          break main;
        }

        min = max;
        max += step;
        sumAtMin = sumAtMax;
      }

      if (previousX <= Number(min) && Number(min) <= nextX) {
        let add = this.integral(0, Number(Number(min) - previousX), Number(slope), Number(previousY));
        sumAtMin = currentValue + add;
      }

      currentValue += this.integral(Number(previousX), Number(nextX), Number(slope), Number(intercept));

      previousX = nextX;
      previousY = nextY;

      if (i < xLength) {
        nextX = x[i];
        nextY = y[i];
        i += 1;
      } else if (i === xLength) {
        nextX += lastOriginalStep;
        nextY = 0;
      }
      // updating parameters
      slope = this.getSlope(previousX, previousY, nextX, nextY);
      intercept = -slope * previousX + previousY;
    }

    return output;
  }

  getEquallySpacedSlot(x: number[], y: number[], from: number, to: number, numberOfPoints: number): number[] {
    let xLength = x.length;
    let step = (to - from) / Number(numberOfPoints - 1);
    let halfStep = step / MLINDEX_BYTES_NUM_2;
    let lastStep = x[x.length - 1] - x[x.length - MLINDEX_BYTES_NUM_2];

    let start = from - halfStep;
    let output: number[] = Array(numberOfPoints).fill(0);
    // Init main variables
    let min = start;
    let max = start + step;

    let previousX = -Number.MAX_VALUE;
    let previousY = 0;
    let nextX = x[0];
    let nextY = y[0];
    let frontOutsideSpectra = 0;
    let backOutsideSpectra = true;

    let currentValue = 0;
    // for slot algorithm
    let currentPoints = 0;

    let i = 1; // index of input
    let j = 0; // index of output
    let mainWhile = true;
    main: while (mainWhile) {
      if (previousX >= nextX) {
        throw new Error('x must be an increasing serie');
      }
      while (previousX - Number(max) > 0) {
        if (backOutsideSpectra) {
          currentPoints += 1;
          backOutsideSpectra = false;
        }

        output[j] = currentPoints <= 0 ? 0 : currentValue / currentPoints;
        j += 1;

        if (j === numberOfPoints) {
          break main;
        }

        min = max;
        max += step;
        currentValue = 0;
        currentPoints = 0;
      }

      if (previousX > Number(min)) {
        currentValue += previousY;
        currentPoints += 1;
      }

      if (previousX === -Number.MAX_VALUE || frontOutsideSpectra > 1) {
        currentPoints -= 1;
      }

      previousX = nextX;
      previousY = nextY;

      if (i < xLength) {
        nextX = x[i];
        nextY = y[i];
        i += 1;
      } else {
        nextX += lastStep;
        nextY = 0;
        frontOutsideSpectra += 1;
      }
    }

    return output;
  }

  integral(x0: number, x1: number, slope: number, intercept: number): number {
    return Number(MLINDEX_BYTES_NUM_05 * slope * x1 * x1 + intercept * x1 - MLINDEX_BYTES_NUM_05 * slope * x0 * x0 + intercept * x0);
  }
}

let mlArrayUtilsGetEquallySpaced = new MlArrayUtilsGetEquallySpacedCls();

// ml-array-utils snv.js
class MlArrayUtilsSnvCls {
  snv(data: number[]): number[] {
    let mean: number = mlStatArray.mean(data);
    let std: number = mlStatArray.standardDeviation(data);
    let result = [...data];
    for (let i = 0; i < data.length; i++) {
      result[i] = Number((Number(result[i]) - mean) / std);
    }
    return result;
  }
}

let mlArrayUtilsSnv = new MlArrayUtilsSnvCls();

// ml-matrix src/util.js
class MlMatrixUtilCls {
  checkRowIndex(matrix: Matrix, index: number, outer: boolean): void {
    let max: number = outer ? matrix.rows : matrix.rows - 1;
    if (index < 0 || index > max) {
      throw new RangeError('Row index out of range');
    }
  }

  checkColumnIndex(matrix: Matrix, index: number, outer: boolean): void {
    let max: number = outer ? matrix.columns : matrix.columns - 1;
    if (index < 0 || index > max) {
      throw new RangeError('Column index out of range');
    }
  }

  checkRowVector(matrix: Matrix, vector: Matrix | number[] | number[][] | number): number[] {
    if (vector instanceof Matrix) {
      return vector.to1dArray();
    }
    if (Array.isArray(vector)) {
      if (vector.length !== matrix.columns) {
        throw new Error('vector size must be the same as the number of columns');
      }
    }
    return vector as number[];
  }

  checkColumnVector(matrix: Matrix, vector: Matrix | number[] | number[][]): number[] {
    if (vector instanceof Matrix) {
      return vector.to1dArray();
    }
    if (Array.isArray(vector)) {
      if (vector.length !== matrix.rows) {
        throw new RangeError('vector size must be the same as the number of rows');
      }
    }
    return vector as number[];
  }

  checkIndices(matrix: Matrix, rowIndices: number[], columnIndices: number[]): [row: number[], column: number[]] {
    let rowOut = rowIndices.some(r => {
      return r < 0 || r >= matrix.rows;
    });

    let columnOut: boolean = columnIndices.some(c => {
      return c < 0 || c >= matrix.columns;
    });

    if (rowOut || columnOut) {
      throw new RangeError('Indices are out of range');
    }
    return [rowIndices, columnIndices];
  }

  checkRange(matrix?: Matrix, startRow?: number, endRow?: number, startColumn?: number, endColumn?: number): void {
    if (matrix === null || startRow === null || endRow === null || startColumn === null || endColumn === null) {
      throw new RangeError('Invalid argument type');
    }
  }

  getRange(from: number, to: number): number[] {
    let arr: number[] = Array(to - from + 1).fill(0);
    for (let i = 0; i < arr.length; i++) {
      arr[i] = from + i;
    }
    return arr;
  }

  sumByRow(matrix: Matrix): Matrix {
    let sum: Matrix = Matrix.zeros(matrix.rows, 1);
    for (let i = 0; i < matrix.rows; ++i) {
      for (let j = 0; j < matrix.columns; ++j) {
        sum.set(i, 0, sum.get(i, 0) + matrix.get(i, j));
      }
    }
    return sum;
  }

  sumByColumn(matrix: Matrix): Matrix {
    let sum: Matrix = Matrix.zeros(1, matrix.columns);
    for (let i = 0; i < matrix.rows; ++i) {
      for (let j = 0; j < matrix.columns; ++j) {
        sum.set(0, j, sum.get(0, j) + matrix.get(i, j));
      }
    }
    return sum;
  }

  sumAll(matrix: Matrix): number {
    let v = 0;
    for (let i = 0; i < matrix.rows; i++) {
      for (let j = 0; j < matrix.columns; j++) {
        v += matrix.get(i, j);
      }
    }
    return v;
  }
}

let mlMatrixUtil = new MlMatrixUtilCls();

// ml-matrix src/dc/util.js
class MlMatrixDcuTilCls {
  hypotenuse(a: number, b: number): number {
    let r: number;
    if (Math.abs(a) > Math.abs(b)) {
      r = b / a;
      return Math.abs(a) * Math.sqrt(1 + r * r);
    }
    if (b !== 0) {
      r = a / b;
      return Math.abs(b) * Math.sqrt(1 + r * r);
    }
    return 0;
  }

  // For use in the decomposition algorithms. With big matrices, access time is
  // too long on elements from array subclass
  // todo check when it is fixed in v8
  // http://jsperf.com/access-and-write-array-subclass
  getEmpty2dArray(rows: number, columns: number): number[][] {
    let array: number[][] = Array(rows).fill([0.0]);
    for (let i = 0; i < rows; i++) {
      array[i] = Array(columns).fill(0.0);
    }
    return array;
  }

  getFilled2dArray(rows: number, columns: number, value: number): number[][] {
    let array: number[][] = Array(rows).fill([0.0]);
    for (let i = 0; i < rows; i++) {
      array[i] = Array(columns).fill(0.0);
      for (let j = 0; j < columns; j++) {
        array[i][j] = value;
      }
    }
    return array;
  }
}

let mlMatrixDcuTil = new MlMatrixDcuTilCls();

// ml-matrix src/dc/lu.js

class MlMatrixDclu {
  lu: Matrix;
  matrix: Matrix;
  pivotVector: number[] = [];
  pivotSign: number = 0.0;

  constructor(matrix: Matrix) {
    let newMatrix = matrix;
    newMatrix = Matrix.checkMatrix(matrix!);
    this.matrix = newMatrix;

    let lu: Matrix = newMatrix.clone();
    let rows: number = lu.rows;
    let columns: number = lu.columns;
    let pivotVector: number[] = Array(rows).fill(0);
    let pivotSign: number = 1.0;
    let p: number;
    let s: number;
    let t: number;
    let v: number;
    let luRowi: number[];
    let luColj: number[];
    let kmax: number;
    for (let i = 0; i < rows; i++) {
      pivotVector[i] = i;
    }

    luColj = Array(rows).fill(0);
    for (let j = 0; j < columns; j++) {
      for (let i = 0; i < rows; i++) {
        luColj[i] = lu.get(i, j);
      }

      for (let i = 0; i < rows; i++) {
        luRowi = lu.getRow(i);
        kmax = Math.min(i, j);
        s = 0;
        for (let k = 0; k < kmax; k++) {
          s += luRowi[k] * luColj[k];
        }
        luColj[i] = luColj[i] - s;
        luRowi[j] = luColj[i];
      }

      p = j;
      for (let i = j + 1; i < rows; i++) {
        if (Math.abs(luColj[i]) > Math.abs(luColj[p])) {
          p = i;
        }
      }

      if (p !== j) {
        for (let k = 0; k < columns; k++) {
          t = lu.get(p, k);
          lu.set(p, k, lu.get(j, k));
          lu.set(j, k, t);
        }

        v = pivotVector[p];
        pivotVector[p] = pivotVector[j];
        pivotVector[j] = v;

        pivotSign = -pivotSign;
      }

      if (j < rows && lu.get(j, j) !== 0) {
        for (let i = j + 1; i < rows; i++) {
          lu.set(i, j, lu.get(i, j) / lu.get(j, j));
        }
      }
    }
    this.lu = lu;
    this.pivotVector = pivotVector;
    this.pivotSign = pivotSign;
  }

  isSingular(): boolean {
    let data: Matrix = this.lu;
    let col: number = data.columns;
    for (let j = 0; j < col; j++) {
      if (data.get(j, j) === 0) {
        return true;
      }
    }
    return false;
  }

  determinant(): number {
    let data: Matrix = this.lu;
    if (!data.isSquare()) {
      throw new Error('Matrix must be square');
    }
    let determinant: number = this.pivotSign;
    let col: number = data.columns;
    for (let j = 0; j < col; j++) {
      determinant *= data.get(j, j);
    }
    return determinant;
  }

  lowerTriangularMatrix(): Matrix {
    let data: Matrix = this.lu;
    let rows: number = data.rows;
    let columns: number = data.columns;
    let x: Matrix = new Matrix(rows, columns);
    for (let i = 0; i < rows; i++) {
      for (let j = 0; j < columns; j++) {
        if (i > j) {
          x.set(i, j, data.get(i, j));
        } else if (i === j) {
          x.set(i, j, 1);
        } else {
          x.set(i, j, 0);
        }
      }
    }
    return x;
  }

  upperTriangularMatrix(): Matrix {
    let data: Matrix = this.lu;
    let rows: number = data.rows;
    let columns: number = data.columns;
    let x: Matrix = new Matrix(rows, columns);
    for (let i = 0; i < rows; i++) {
      for (let j = 0; j < columns; j++) {
        if (i <= j) {
          x.set(i, j, data.get(i, j));
        } else {
          x.set(i, j, 0);
        }
      }
    }
    return x;
  }

  pivotPermutationVector(): number[] {
    return [...this.pivotVector];
  }

  solve(value: Matrix): Matrix {
    value = Matrix.checkMatrix(value);

    let lu = this.lu;
    let rows: number = lu.rows;

    if (rows !== value.rows) {
      throw new Error('Invalid matrix dimensions');
    }
    if (this.isSingular()) {
      throw new Error('LU matrix is singular');
    }

    let count: number = value.columns;
    let x: Matrix = value.subMatrixRow(this.pivotVector, 0, count - 1);
    let columns: number = lu.columns;

    for (let k = 0; k < columns; k++) {
      for (let i = k + 1; i < columns; i++) {
        for (let j = 0; j < count; j++) {
          x.set(i, j, x.get(i, j) - x.get(k, j) * lu.get(i, k));
        }
      }
    }
    for (let k = columns - 1; k >= 0; k--) {
      for (let j = 0; j < count; j++) {
        x.set(k, j, x.get(k, j) / lu.get(k, k));
      }
      for (let i = 0; i < k; i++) {
        for (let j = 0; j < count; j++) {
          x.set(i, j, x.get(i, j) - x.get(k, j) * lu.get(i, k));
        }
      }
    }
    return x;
  }
}

// ml-matrix src/dc/svd.js
class MlMatrixDcsvd {
  m: number = 0;
  n: number = 0;
  s: number[] = [];
  u?: number | number[][] | Matrix;
  v?: number | number[][] | Matrix;

  constructor(value: Matrix, options: MlOptions) {
    value = Matrix.checkMatrix(value!);
    let m: number = value.rows;
    let n: number = value.columns;
    let nu: number = Math.min(m, n);

    let wantu = true;
    let wantv = true;
    if (options.computeLeftSingularVectors === false) {
      wantu = false;
    }
    if (options.computeRightSingularVectors === false) {
      wantv = false;
    }
    let autoTranspose = options.autoTranspose === true;

    let swapped = false;
    let a: Matrix;
    if (m < n) {
      if (!autoTranspose) {
        a = value.clone();
        // eslint-disable-next-line no-console
        print('Computing SVD on a matrix with more columns than rows. Consider enabling autoTranspose');
      } else {
        a = value.transpose();
        m = a.rows;
        n = a.columns;
        swapped = true;
        let aux = wantu;
        wantu = wantv;
        wantv = aux;
      }
    } else {
      a = value.clone();
    }
    let s: number[] = Array(Math.min(m + 1, n)).fill(0.0);
    let u: number[][] = mlMatrixDcuTil.getFilled2dArray(m, nu, 0);
    let v: number[][] = mlMatrixDcuTil.getFilled2dArray(n, n, 0);
    let e: number[] = Array(n).fill(0.0);
    let work: number[] = Array(m).fill(0.0);

    let nct = Math.min(m - 1, n);
    let nrt = Math.max(0, Math.min(n - MLINDEX_BYTES_NUM_2, m));

    let p: number;
    let t: number;
    let f: number;
    let cs: number;
    let sn: number;
    let kase: number;
    let scale: number;
    let sp: number;
    let spm1: number;
    let epm1: number;
    let sk: number;
    let ek: number;
    let b: number;
    let c: number;
    let shift: number;
    let g: number;

    for (let k = 0; k < Math.max(nct, nrt); k++) {
      if (k < nct) {
        s[k] = 0;
        for (let i = k; i < m; i++) {
          s[k] = mlMatrixDcuTil.hypotenuse(s[k], a.get(i, k));
        }
        if (s[k] !== 0) {
          if (a.get(k, k) < 0) {
            s[k] = -s[k];
          }
          for (let i = k; i < m; i++) {
            a.set(i, k, a.get(i, k) / s[k]);
          }
          a.set(k, k, a.get(k, k) + 1);
        }
        s[k] = -s[k];
      }

      for (let j = k + 1; j < n; j++) {
        if (k < nct && s[k] !== 0) {
          t = 0;
          for (let i = k; i < m; i++) {
            t += a.get(i, k) * a.get(i, j);
          }
          t = -t / a.get(k, k);
          for (let i = k; i < m; i++) {
            a.set(i, j, a.get(i, j) + t * a.get(i, k));
          }
        }
        e[j] = a.get(k, j);
      }

      if (wantu && k < nct) {
        for (let i = k; i < m; i++) {
          u[i][k] = a.get(i, k);
        }
      }

      if (k < nrt) {
        e[k] = 0;
        for (let i = k + 1; i < n; i++) {
          e[k] = mlMatrixDcuTil.hypotenuse(e[k], e[i]);
        }
        if (e[k] !== 0) {
          if (e[k + 1] < 0) {
            e[k] = 0 - e[k];
          }
          for (let i = k + 1; i < n; i++) {
            e[i] /= e[k];
          }
          e[k + 1] += 1;
        }
        e[k] = -e[k];
        if (k + 1 < m && e[k] !== 0) {
          for (let i = k + 1; i < m; i++) {
            work[i] = 0.0;
          }
          for (let j = k + 1; j < n; j++) {
            for (let i = k + 1; i < m; i++) {
              work[i] += e[j] * a.get(i, j);
            }
          }
          for (let j = k + 1; j < n; j++) {
            t = -e[j] / e[k + 1];
            for (let i = k + 1; i < m; i++) {
              a.set(i, j, a.get(i, j) + t * work[i]);
            }
          }
        }
        if (wantv) {
          for (let i = k + 1; i < n; i++) {
            v[i][k] = e[i];
          }
        }
      }
    }
    p = Math.min(n, m + 1);
    if (nct < n) {
      s[nct] = a.get(nct, nct);
    }
    if (m < p) {
      s[p - 1] = 0;
    }
    if (nrt + 1 < p) {
      e[nrt] = a.get(nrt, p - 1);
    }
    e[p - 1] = 0;

    if (wantu) {
      for (let j = nct; j < nu; j++) {
        for (let i = 0; i < m; i++) {
          u[i][j] = 0;
        }
        u[j][j] = 1;
      }
      for (let k = nct - 1; k >= 0; k--) {
        if (s[k] !== 0) {
          for (let j = k + 1; j < nu; j++) {
            t = 0;
            for (let i = k; i < m; i++) {
              t += u[i][k] * u[i][j];
            }
            t = -t / u[k][k];
            for (let i = k; i < m; i++) {
              u[i][j] += t * u[i][k];
            }
          }
          for (let i = k; i < m; i++) {
            u[i][k] = -Number(u[i][k]);
          }
          u[k][k] = 1 + u[k][k];
          for (let i = 0; i < k - 1; i++) {
            u[i][k] = 0;
          }
        } else {
          for (let i = 0; i < m; i++) {
            u[i][k] = 0;
          }
          u[k][k] = 1;
        }
      }
    }

    if (wantv) {
      for (let k = n - 1; k >= 0; k--) {
        if (k < nrt && e[k] !== 0) {
          for (let j = k + 1; j < n; j++) {
            t = 0;
            for (let i = k + 1; i < n; i++) {
              t += v[i][k] * v[i][j];
            }
            t = -t / v[k + 1][k];
            for (let i = k + 1; i < n; i++) {
              v[i][j] += t * v[i][k];
            }
          }
        }
        for (let i = 0; i < n; i++) {
          v[i][k] = 0;
        }
        v[k][k] = 1;
      }
    }

    let pp = p - 1;
    let iter = 0;
    let eps = Math.pow(MLINDEX_BYTES_NUM_2, MLINDEX_BYTES_NUM_52);
    while (p > 0) {
      let k = p - MLINDEX_BYTES_NUM_2;
      for (k; k >= -1; k--) {
        if (k === -1) {
          break;
        }
        if (Math.abs(e[k]) <= eps * (Math.abs(s[k]) + Math.abs(s[k + 1]))) {
          e[k] = 0;
          break;
        }
      }
      if (k === p - MLINDEX_BYTES_NUM_2) {
        kase = MLINDEX_BYTES_NUM_4;
      } else {
        let ks = p - 1;
        for (ks; ks >= k; ks--) {
          if (ks === k) {
            break;
          }
          t = (ks !== p ? Math.abs(e[ks]) : 0) + (ks !== k + 1 ? Math.abs(e[ks - 1]) : 0);
          if (Math.abs(s[ks]) <= eps * t) {
            s[ks] = 0;
            break;
          }
        }
        if (ks === k) {
          kase = MLINDEX_BYTES_NUM_3;
        } else if (ks === p - 1) {
          kase = 1;
        } else {
          kase = MLINDEX_BYTES_NUM_2;
          k = ks;
        }
      }
      k += 1;
      switch (kase) {
        case 1: {
          f = e[p - MLINDEX_BYTES_NUM_2];
          e[p - MLINDEX_BYTES_NUM_2] = 0;
          for (let j = p - MLINDEX_BYTES_NUM_2; j >= k; j--) {
            t = mlMatrixDcuTil.hypotenuse(s[j], f);
            cs = s[j] / t;
            sn = f / t;
            s[j] = t;
            if (j !== k) {
              f = -sn * e[j - 1];
              e[j - 1] = cs * e[j - 1];
            }
            if (wantv) {
              for (let i = 0; i < n; i++) {
                t = cs * v[i][j] + sn * v[i][p - 1];
                v[i][p - 1] = -sn * v[i][j] + cs * v[i][p - 1];
                v[i][j] = t;
              }
            }
          }
          break;
        }
        case MLINDEX_BYTES_NUM_2: {
          f = e[k - 1];
          e[k - 1] = 0;
          for (let j = k; j < p; j++) {
            t = mlMatrixDcuTil.hypotenuse(s[j], f);
            cs = s[j] / t;
            sn = f / t;
            s[j] = t;
            f = -sn * e[j];
            e[j] = cs * e[j];
            if (wantu) {
              for (let i = 0; i < m; i++) {
                t = cs * u[i][j] + sn * u[i][k - 1];
                u[i][k - 1] = -sn * u[i][j] + cs * u[i][k - 1];
                u[i][j] = t;
              }
            }
          }
          break;
        }
        case MLINDEX_BYTES_NUM_3: {
          scale = Math.max(
            Math.max(Math.max(Math.max(Math.abs(s[p - 1]), Math.abs(s[p - MLINDEX_BYTES_NUM_2])), Math.abs(e[p - MLINDEX_BYTES_NUM_2])), Math.abs(s[k])),
            Math.abs(e[k])
          );
          sp = s[p - 1] / scale;
          spm1 = s[p - MLINDEX_BYTES_NUM_2] / scale;
          epm1 = e[p - MLINDEX_BYTES_NUM_2] / scale;
          sk = s[k] / scale;
          ek = e[k] / scale;
          b = ((spm1 + sp) * (spm1 - sp) + epm1 * epm1) / MLINDEX_BYTES_NUM_2;
          c = sp * epm1 * (sp * epm1);
          shift = 0;
          if (b !== 0 || c !== 0) {
            shift = Math.sqrt(b * b + c);
            if (b < 0) {
              shift = -shift;
            }
            shift = c / (b + shift);
          }
          f = (sk + sp) * (sk - sp) + shift;
          g = sk * ek;
          for (let j = k; j < p - 1; j++) {
            t = mlMatrixDcuTil.hypotenuse(f, g);
            cs = f / t;
            sn = g / t;
            if (j !== k) {
              e[j - 1] = t;
            }
            f = cs * s[j] + sn * e[j];
            e[j] = cs * e[j] - sn * s[j];
            g = sn * s[j + 1];
            s[j + 1] = cs * s[j + 1];
            if (wantv) {
              for (let i = 0; i < n; i++) {
                t = cs * v[i][j] + sn * v[i][j + 1];
                v[i][j + 1] = -sn * v[i][j] + cs * v[i][j + 1];
                v[i][j] = t;
              }
            }
            t = mlMatrixDcuTil.hypotenuse(f, g);
            cs = f / t;
            sn = g / t;
            s[j] = t;
            f = cs * e[j] + sn * s[j + 1];
            s[j + 1] = -sn * e[j] + cs * s[j + 1];
            g = sn * e[j + 1];
            e[j + 1] = cs * e[j + 1];
            if (wantu && j < m - 1) {
              for (let i = 0; i < m; i++) {
                t = cs * u[i][j] + sn * u[i][j + 1];
                u[i][j + 1] = -sn * u[i][j] + cs * u[i][j + 1];
                u[i][j] = t;
              }
            }
          }
          e[p - MLINDEX_BYTES_NUM_2] = f;
          iter = iter + 1;
          break;
        }
        case MLINDEX_BYTES_NUM_4: {
          if (s[k] <= 0) {
            s[k] = s[k] < 0 ? -s[k] : 0;
            if (wantv) {
              for (let i = 0; i <= pp; i++) {
                v[i][k] = -v[i][k];
              }
            }
          }
          while (k < pp) {
            if (s[k] >= s[k + 1]) {
              break;
            }
            t = s[k];
            s[k] = s[k + 1];
            s[k + 1] = t;
            if (wantv && k < n - 1) {
              for (let i = 0; i < n; i++) {
                t = v[i][k + 1];
                v[i][k + 1] = v[i][k];
                v[i][k] = t;
              }
            }
            if (wantu && k < m - 1) {
              for (let i = 0; i < m; i++) {
                t = u[i][k + 1];
                u[i][k + 1] = u[i][k];
                u[i][k] = t;
              }
            }
            k += 1;
          }
          iter = 0;
          p -= 1;
          break;
        }
        default:
          break;
      }
    }

    if (swapped) {
      let tmp = v;
      v = u;
      u = tmp;
    }
    this.m = m;
    this.n = n;
    this.s = s;
    this.u = u;
    this.v = v;
  }

  condition(): number {
    return this.s[0] / this.s[Math.min(this.m, this.n) - 1];
  }

  norm2(): number {
    return this.s[0];
  }

  rank(): number {
    let eps = Math.pow(MLINDEX_BYTES_NUM_2, MLINDEX_BYTES_NUM_52);
    let tol = Math.max(this.m, this.n) * this.s[0] * eps;
    let r = 0.0;
    let s = this.s;
    for (let i = 0, ii = s.length; i < ii; i++) {
      if (s[i] > tol) {
        r += 1;
      }
    }
    return r;
  }

  diagonal(): number[] {
    return this.s;
  }

  threshold(): number {
    return (Math.pow(MLINDEX_BYTES_NUM_2, MLINDEX_BYTES_NUM_52) / MLINDEX_BYTES_NUM_2) * Math.max(this.m, this.n) * this.s[0];
  }

  leftSingularVectors(): number | number[] | number[][] | Matrix {
    if (!Matrix.isMatrix(this.u)) {
      return new Matrix(this.u);
    }
    return this.u as number | number[] | number[][] | Matrix;
  }

  rightSingularVectors(): number | number[] | number[][] | Matrix {
    if (!Matrix.isMatrix(this.v)) {
      return new Matrix(this.v);
    }
    return this.v as number | number[] | number[][] | Matrix;
  }

  diagonalMatrix(): Matrix {
    return Matrix.diag(this.s);
  }

  solve(value: Matrix): Matrix {
    let y = value;
    let e = this.threshold;
    let scols: number = this.s.length;
    let ls: Matrix = Matrix.zeros(scols, scols);

    for (let i = 0; i < scols; i++) {
      if (Math.abs(this.s[i]) <= Number(e)) {
        ls.set(i, i, 0);
      } else {
        ls.set(i, i, 1 / this.s[i]);
      }
    }

    let u = this.u as number[][];
    let v = this.rightSingularVectors() as Matrix;

    let vl: Matrix = v.mmul(ls);
    let vRows: number = v!.rows;
    let uRows = u.length;
    let vlu: Matrix = Matrix.zeros(vRows, uRows);
    let sum: number;

    for (let i = 0; i < vRows; i++) {
      for (let j = 0; j < uRows; j++) {
        sum = 0;
        for (let k = 0; k < scols; k++) {
          sum += vl.get(i, k) * u[j][k];
        }
        vlu.set(i, j, sum);
      }
    }

    return vlu.mmul(y);
  }

  solveForDiagonal(value: number[]): Matrix {
    return this.solve(Matrix.diag(value));
  }

  inverse(): Matrix {
    let v = this.v as number[][];
    let e = this.threshold();
    let vRows: number = v.length;
    let vCols: number = (v[0] as number[]).length;
    let x: Matrix = new Matrix(vRows, this.s.length);

    for (let i = 0; i < vRows; i++) {
      for (let j = 0; j < vCols; j++) {
        if (Math.abs(this.s[j]) > Number(e)) {
          x.set(i, j, Number(v[i]) / this.s[j]);
        } else {
          x.set(i, j, 0);
        }
      }
    }

    let u = this.u! as number[][];

    let uRows: number = (u as number[][]).length;
    let uCols: number = (u[0] as number[]).length;
    let y: Matrix = new Matrix(vRows, uRows);
    let sum: number;

    for (let i = 0; i < vRows; i++) {
      for (let j = 0; j < uRows; j++) {
        sum = 0.0;
        for (let k = 0; k < uCols; k++) {
          sum += x.get(i, k) * u[j][k];
        }
        y.set(i, j, sum);
      }
    }
    return y;
  }
}

// ml-matrix src/abstractMatrix.js
class Matrix {
  rows: number = 0;
  columns: number = 0;
  array?: number[][];

  constructor(nRows?: number | Matrix | Array<Array<number>>, nColumns?: number) {
    if (nRows instanceof Matrix) {
      this.array = nRows.array;
      this.rows = nRows.rows;
      this.columns = nRows.columns;
    } else if (typeof nRows === 'number' && nRows > 0) {
      this.array = Array(nRows).fill(0.0);
      if (nColumns !== undefined && nColumns > 0) {
        for (let i = 0; i < nRows; i++) {
          this.array[i] = Array(nColumns).fill(0.0);
        }
        this.rows = nRows;
        this.columns = nColumns;
      } else {
        throw new Error('nColumns must be a positive integer');
      }
    } else if (nRows instanceof Array && nRows.length > 0 && nRows[0] instanceof Array) {
      this.array = nRows;
      this.rows = nRows.length;
      this.columns = nRows[0].length;
    } else {
      throw new Error('First argument must be a positive number or an array');
    }
  }

  set(rowIndex: number, columnIndex: number, value: number): Matrix {
    this.array![rowIndex][columnIndex] = value;
    return this;
  }

  get(rowIndex: number, columnIndex: number): number {
    return this.array![rowIndex][columnIndex];
  }

  clone(): Matrix {
    let newMatrix = new Matrix(this.rows, this.columns);
    for (let row = 0; row < this.rows; row++) {
      for (let column = 0; column < this.columns; column++) {
        newMatrix.set(row, column, this.get(row, column));
      }
    }
    return newMatrix;
  }

  static from1DArray(newRows: number, newColumns: number, newData: number[]): Matrix {
    let length = newRows * newColumns;
    if (length !== newData.length) {
      throw new RangeError('Data length does not match given dimensions');
    }
    let newMatrix = new Matrix(newRows, newColumns);

    for (let row = 0; row < newRows; row++) {
      for (let column = 0; column < newColumns; column++) {
        newMatrix.set(row, column, newData[row * newColumns + column]);
      }
    }
    return newMatrix;
  }

  static rowVector(newData: number[]): Matrix {
    let vector = new Matrix(1, newData.length);
    for (let i = 0; i < newData.length; i++) {
      vector.set(0, i, newData[i]);
    }
    return vector;
  }

  static columnVector(newData: number[]): Matrix {
    let vector = new Matrix(newData.length, 1);
    for (let i = 0; i < newData.length; i++) {
      vector.set(i, 0, newData[i]);
    }
    return vector;
  }

  static empty(rows: number, columns: number): Matrix {
    return new Matrix(rows, columns);
  }

  static zeros(rows: number, columns: number): Matrix {
    return Matrix.empty(rows, columns).fill(0.0);
  }

  static ones(rows: number, columns: number): Matrix {
    return Matrix.empty(rows, columns).fill(1.0);
  }

  static rand(rows: number, columns: number, rng?: number): Matrix {
    rng ?? Math.random();
    let matrix = Matrix.empty(rows, columns);
    for (let i = 0; i < rows; i++) {
      for (let j = 0; j < columns; j++) {
        matrix.set(i, j, Math.random());
      }
    }
    return matrix;
  }

  static randInt(rows: number, columns: number, maxValue?: number, rng?: number): Matrix {
    let maxV = maxValue ?? MLINDEX_BYTES_NUM_1000;
    let matrix = Matrix.empty(rows, columns);
    for (let i = 0; i < rows; i++) {
      for (let j = 0; j < columns; j++) {
        let value = Math.floor(Math.random() * maxV);
        matrix.set(i, j, value);
      }
    }
    return matrix;
  }

  static eye(rows: number, columns?: number, value?: number): Matrix {
    let newColumns = columns;
    let newValue = value;

    if (columns === undefined) {
      newColumns = rows;
    }
    if (value === undefined) {
      newValue = 1;
    }
    let min = Math.min(rows, newColumns!);
    let matrix = Matrix.zeros(rows, newColumns!);
    for (let i = 0; i < min; i++) {
      matrix.set(i, i, newValue!);
    }
    return matrix;
  }

  static diag(data: number[], rows?: number, columns?: number): Matrix {
    let newRows = rows;
    let newColumns = columns;
    let l = data.length;
    if (rows === undefined) {
      newRows = l;
    }
    if (columns === undefined) {
      newColumns = rows;
    }
    let min = Math.min(l, newRows!, newColumns!);
    let matrix = Matrix.zeros(newRows!, newColumns!);
    for (let i = 0; i < min; i++) {
      matrix.set(i, i, data[i]);
    }
    return matrix;
  }

  static min(matrix1: Matrix, matrix2: Matrix): Matrix {
    let matrix11 = Matrix.checkMatrix(matrix1);
    let matrix22 = Matrix.checkMatrix(matrix2);
    let rows: number = matrix11.rows;
    let columns: number = matrix11.columns;
    let result = new Matrix(rows, columns);
    for (let i = 0; i < rows; i++) {
      for (let j = 0; j < columns; j++) {
        result.set(i, j, Math.min(matrix11.get(i, j), matrix22.get(i, j)));
      }
    }
    return result;
  }

  static max(matrix1: Matrix, matrix2: Matrix): Matrix {
    let matrix11 = Matrix.checkMatrix(matrix1);
    let matrix22 = Matrix.checkMatrix(matrix2);
    let rows: number = matrix11.rows;
    let columns: number = matrix11.columns;
    let result = new Matrix(rows, columns);
    for (let i = 0; i < rows; i++) {
      for (let j = 0; j < columns; j++) {
        result.set(i, j, Math.max(matrix11.get(i, j), matrix22.get(i, j)));
      }
    }
    return result;
  }

  static checkMatrix(value: number | number[][] | Matrix): Matrix {
    return Matrix.isMatrix(value) ? (value as Matrix) : new Matrix(value);
  }

  static isMatrix(value?: number | number[] | number[][] | Matrix): boolean {
    return value !== null && value instanceof Matrix;
  }

  size(): number {
    return this.rows * this.columns;
  }

  apply(callback: (matrix: Matrix, row: number, column: number) => void): Matrix {
    let ii = this.rows;
    let jj = this.columns;
    for (let i = 0; i < ii; i++) {
      for (let j = 0; j < jj; j++) {
        callback(this, i, j);
      }
    }
    return this;
  }

  to1dArray(): number[] {
    let array: number[] = Array(this.size()).fill(0.0);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        array[i * this.columns + j] = this.get(i, j);
      }
    }
    return array;
  }

  to2dArray(): number[][] {
    let copy: number[][] = Array(this.rows).fill([0.0]);
    for (let i = 0; i < this.rows; i++) {
      copy[i] = Array(this.columns).fill(0);
      for (let j = 0; j < this.columns; j++) {
        copy[i][j] = this.get(i, j);
      }
    }
    return copy;
  }

  isRowVector(): boolean {
    return this.rows === 1;
  }

  isColumnVector(): boolean {
    return this.columns === 1;
  }

  isVector(): boolean {
    return this.rows === 1 || this.columns === 1;
  }

  isSquare(): boolean {
    return this.rows === this.columns;
  }

  isSymmetric(): boolean {
    if (this.isSquare()) {
      for (let i = 0; i < this.rows; i++) {
        for (let j = 0; j <= i; j++) {
          if (this.get(i, j) !== this.get(j, i)) {
            return false;
          }
        }
      }
      return true;
    }
    return false;
  }

  repeat(rowRep: number, colRep: number): Matrix {
    let newRowRep = rowRep > 0 ? rowRep : 1;
    let newColRep = colRep > 0 ? colRep : 1;
    let matrix = new Matrix(this.rows * newRowRep, this.columns * newColRep);
    for (let i = 0; i < newRowRep; i++) {
      for (let j = 0; j < newColRep; j++) {
        matrix.setSubMatrix(this, this.rows * i, this.columns * j);
      }
    }
    return matrix;
  }

  fill(value: number): Matrix {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, value);
      }
    }
    return this;
  }

  neg(): Matrix {
    return this.mulS(-1);
  }

  getRow(index: number): number[] {
    mlMatrixUtil.checkRowIndex(this, index, false);
    let row: number[] = Array(this.columns).fill(0.0);
    for (let i = 0; i < this.columns; i++) {
      row[i] = this.get(index, i);
    }
    return row;
  }

  getRowVector(index: number): Matrix {
    return Matrix.rowVector(this.getRow(index));
  }

  setRow(index: number, array: Matrix): Matrix {
    mlMatrixUtil.checkRowIndex(this, index, false);
    for (let i = 0; i < this.columns; i++) {
      this.set(index, i, array.get(i, i));
    }
    return this;
  }

  swapRows(row1: number, row2: number): Matrix {
    mlMatrixUtil.checkRowIndex(this, row1, false);
    mlMatrixUtil.checkRowIndex(this, row2, false);
    for (let i = 0; i < this.columns; i++) {
      let temp = this.get(row1, i);
      this.set(row1, i, this.get(row2, i));
      this.set(row2, i, temp);
    }
    return this;
  }

  getColumn(index: number): number[] {
    mlMatrixUtil.checkColumnIndex(this, index, false);
    let column: number[] = Array(this.rows).fill(0.0);
    for (let i = 0; i < this.rows; i++) {
      column[i] = this.get(i, index);
    }
    return column;
  }

  getColumnVector(index: number): Matrix {
    return Matrix.columnVector(this.getColumn(index));
  }

  setColumn(index: number, array: number[]): Matrix {
    mlMatrixUtil.checkColumnIndex(this, index, false);
    for (let i = 0; i < this.rows; i++) {
      this.set(i, index, array[i]);
    }
    return this;
  }

  swapColumns(column1: number, column2: number): Matrix {
    mlMatrixUtil.checkColumnIndex(this, column1, false);
    mlMatrixUtil.checkColumnIndex(this, column2, false);
    for (let i = 0; i < this.rows; i++) {
      let temp = this.get(i, column1);
      this.set(i, column1, this.get(i, column2));
      this.set(i, column2, temp);
    }
    return this;
  }

  addRowVector(vector: Matrix): Matrix {
    let newVector: number[] = mlMatrixUtil.checkRowVector(this, vector);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) + Number(newVector[j]));
      }
    }
    return this;
  }

  subRowVector(vector: number[]): Matrix {
    let newVector = mlMatrixUtil.checkRowVector(this, vector);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) - newVector[j]);
      }
    }
    return this;
  }

  mulRowVector(vector: number[]): Matrix {
    let newVector = mlMatrixUtil.checkRowVector(this, vector);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) * newVector[j]);
      }
    }
    return this;
  }

  divRowVector(vector: number[]): Matrix {
    let newVector = mlMatrixUtil.checkRowVector(this, vector);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) / newVector[j]);
      }
    }
    return this;
  }

  addColumnVector(vector: number[]): Matrix {
    let newVector = mlMatrixUtil.checkRowVector(this, vector);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) + newVector[i]);
      }
    }
    return this;
  }

  subColumnVector(vector: number[]): Matrix {
    let newVector = mlMatrixUtil.checkColumnVector(this, vector);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) - newVector[i]);
      }
    }
    return this;
  }

  mulColumnVector(vector: number[]): Matrix {
    let newVector = mlMatrixUtil.checkColumnVector(this, vector);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) * newVector[i]);
      }
    }
    return this;
  }

  divColumnVector(vector: Matrix): Matrix {
    let newVector: number[] = mlMatrixUtil.checkColumnVector(this, vector);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) / Number(newVector[i]));
      }
    }
    return this;
  }

  mulRow(index: number, value: number): Matrix {
    mlMatrixUtil.checkRowIndex(this, index, false);
    for (let i = 0; i < this.columns; i++) {
      this.set(index, i, this.get(index, i) * value);
    }
    return this;
  }

  mulColumn(index: number, value: number): Matrix {
    mlMatrixUtil.checkColumnIndex(this, index, false);
    for (let i = 0; i < this.rows; i++) {
      this.set(i, index, this.get(i, index) * value);
    }
    return this;
  }

  max(): number {
    let v = this.get(0, 0);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        if (this.get(i, j) > v) {
          v = this.get(i, j);
        }
      }
    }
    return v;
  }

  maxIndex(): number[] {
    let v = this.get(0, 0);
    let idx = [0, 0];
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        if (this.get(i, j) > v) {
          v = this.get(i, j);
          idx[0] = i;
          idx[1] = j;
        }
      }
    }
    return idx;
  }

  min(): number {
    let v = this.get(0, 0);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        if (this.get(i, j) < v) {
          v = this.get(i, j);
        }
      }
    }
    return v;
  }

  minIndex(): number[] {
    let v = this.get(0, 0);
    let idx = [0, 0];
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        if (this.get(i, j) < v) {
          v = this.get(i, j);
          idx[0] = i;
          idx[1] = j;
        }
      }
    }
    return idx;
  }

  maxRow(row: number): number {
    mlMatrixUtil.checkRowIndex(this, row, false);
    let v = this.get(row, 0);
    for (let i = 1; i < this.columns; i++) {
      if (this.get(row, i) > v) {
        v = this.get(row, i);
      }
    }
    return v;
  }

  maxRowIndex(row: number): number[] {
    mlMatrixUtil.checkRowIndex(this, row, false);
    let v = this.get(row, 0);
    let idx = [row, 0];
    for (let i = 1; i < this.columns; i++) {
      if (this.get(row, i) > v) {
        v = this.get(row, i);
        idx[1] = i;
      }
    }
    return idx;
  }

  minRow(row: number): number {
    mlMatrixUtil.checkRowIndex(this, row, false);
    let v = this.get(row, 0);
    for (let i = 1; i < this.columns; i++) {
      if (this.get(row, i) < v) {
        v = this.get(row, i);
      }
    }
    return v;
  }

  minRowIndex(row: number): number[] {
    mlMatrixUtil.checkRowIndex(this, row, false);
    let v = this.get(row, 0);
    let idx = [row, 0];
    for (let i = 1; i < this.columns; i++) {
      if (this.get(row, i) < v) {
        v = this.get(row, i);
        idx[1] = i;
      }
    }
    return idx;
  }

  maxColumn(column: number): number {
    mlMatrixUtil.checkColumnIndex(this, column, false);
    let v = this.get(0, column);
    for (let i = 1; i < this.rows; i++) {
      if (this.get(i, column) > v) {
        v = this.get(i, column);
      }
    }
    return v;
  }

  maxColumnIndex(column: number): number[] {
    mlMatrixUtil.checkColumnIndex(this, column, false);
    let v = this.get(0, column);
    let idx = [0, column];
    for (let i = 1; i < this.rows; i++) {
      if (this.get(i, column) > v) {
        v = this.get(i, column);
        idx[0] = i;
      }
    }
    return idx;
  }

  minColumn(column: number): number {
    mlMatrixUtil.checkColumnIndex(this, column, false);
    let v = this.get(0, column);
    for (let i = 1; i < this.rows; i++) {
      if (this.get(i, column) < v) {
        v = this.get(i, column);
      }
    }
    return v;
  }

  minColumnIndex(column: number): number[] {
    mlMatrixUtil.checkColumnIndex(this, column, false);
    let v = this.get(0, column);
    let idx = [0, column];
    for (let i = 1; i < this.rows; i++) {
      if (this.get(i, column) < v) {
        v = this.get(i, column);
        idx[0] = i;
      }
    }
    return idx;
  }

  diag(): number[] {
    let min = Math.min(this.rows, this.columns);
    let diag: number[] = Array(min).fill(0.0);
    for (let i = 0; i < min; i++) {
      diag[i] = this.get(i, i);
    }
    return diag;
  }

  sum(by: string): number | number[] | number[][] | Matrix {
    switch (by) {
      case 'row':
        return mlMatrixUtil.sumByRow(this);
      case 'column':
        return mlMatrixUtil.sumByColumn(this);
      default:
        return mlMatrixUtil.sumAll(this);
    }
  }

  mean(): number {
    return (this.sum('') as number) / Number(this.size);
  }

  prod(): number {
    let prod = 1.0;
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        prod *= this.get(i, j);
      }
    }
    return prod;
  }

  cumulativeSum(): Matrix {
    let sum = 0.0;
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        sum += this.get(i, j);
        this.set(i, j, sum);
      }
    }
    return this;
  }

  dot(vector2: Matrix): number {
    let vector2D1: number[] = [];
    if (Matrix.isMatrix(vector2)) {
      vector2D1 = vector2.to1dArray();
    }
    let vector1D1: number[] = this.to1dArray();
    if (vector1D1.length !== vector2D1.length) {
      throw new RangeError('vectors do not have the same size');
    }
    let dot = 0.0;
    for (let i = 0; i < vector1D1.length; i++) {
      dot += vector1D1[i] * vector2D1[i];
    }
    return dot;
  }

  mmul(other: Matrix): Matrix {
    other = Matrix.checkMatrix(other);
    if (this.columns !== other.rows) {
      throw new RangeError('Number of columns of left matrix are not equal to number of rows of right matrix.');
    }

    let m: number = this.rows;
    let n: number = this.columns;
    let p: number = other.columns;

    let result = new Matrix(m, p);

    let bColj: number[] = Array(n).fill(0.0);
    for (let j = 0; j < p; j++) {
      for (let k = 0; k < n; k++) {
        bColj[k] = other.get(k, j);
      }

      for (let i = 0; i < m; i++) {
        let s = 0.0;
        for (let k = 0; k < n; k++) {
          s += this.get(i, k) * bColj[k];
        }
        result.set(i, j, s);
      }
    }
    return result;
  }

  strassen2x2(other: Matrix): Matrix {
    let result = new Matrix(MLINDEX_BYTES_NUM_2, MLINDEX_BYTES_NUM_2);
    let a11 = this.get(0, 0);
    let b11 = other.get(0, 0);
    let a12 = this.get(0, 1);
    let b12 = other.get(0, 1);
    let a21 = this.get(1, 0);
    let b21 = other.get(1, 0);
    let a22 = this.get(1, 1);
    let b22 = other.get(1, 1);

    let m1 = (a11 + a22) * (b11 + b22);
    let m2 = (a21 + a22) * b11;
    let m3 = a11 * (b12 - b22);
    let m4 = a22 * (b21 - b11);
    let m5 = (a11 + a12) * b22;
    let m6 = (a21 - a11) * (b11 + b12);
    let m7 = (a12 - a22) * (b21 + b22);

    let c00 = m1 + m4 - m5 + m7;
    let c01 = m3 + m5;
    let c10 = m2 + m4;
    let c11 = m1 - m2 + m3 + m6;

    result.set(0, 0, c00);
    result.set(0, 1, c01);
    result.set(1, 0, c10);
    result.set(1, 1, c11);
    return result;
  }

  strassen3x3(other: Matrix): Matrix {
    let result = new Matrix(MLINDEX_BYTES_NUM_3, MLINDEX_BYTES_NUM_3);

    let a00 = this.get(0, 0);
    let a01 = this.get(0, 1);
    let a02 = this.get(0, MLINDEX_BYTES_NUM_2);
    let a10 = this.get(1, 0);
    let a11 = this.get(1, 1);
    let a12 = this.get(1, MLINDEX_BYTES_NUM_2);
    let a20 = this.get(MLINDEX_BYTES_NUM_2, 0);
    let a21 = this.get(MLINDEX_BYTES_NUM_2, 1);
    let a22 = this.get(MLINDEX_BYTES_NUM_2, MLINDEX_BYTES_NUM_2);

    let b00 = other.get(0, 0);
    let b01 = other.get(0, 1);
    let b02 = other.get(0, MLINDEX_BYTES_NUM_2);
    let b10 = other.get(1, 0);
    let b11 = other.get(1, 1);
    let b12 = other.get(1, MLINDEX_BYTES_NUM_2);
    let b20 = other.get(MLINDEX_BYTES_NUM_2, 0);
    let b21 = other.get(MLINDEX_BYTES_NUM_2, 1);
    let b22 = other.get(MLINDEX_BYTES_NUM_2, MLINDEX_BYTES_NUM_2);

    let m1 = (a00 + a01 + a02 - a10 - a11 - a21 - a22) * b11;
    let m2 = (a00 - a10) * (-b01 + b11);
    let m3 = a11 * (-b00 + b01 + b10 - b11 - b12 - b20 + b22);
    let m4 = (-a00 + a10 + a11) * (b00 - b01 + b11);
    let m5 = (a10 + a11) * (-b00 + b01);
    let m6 = a00 * b00;
    let m7 = (-a00 + a20 + a21) * (b00 - b02 + b12);
    let m8 = (-a00 + a20) * (b02 - b12);
    let m9 = (a20 + a21) * (-b00 + b02);
    let m10 = (a00 + a01 + a02 - a11 - a12 - a20 - a21) * b12;
    let m11 = a21 * (-b00 + b02 + b10 - b11 - b12 - b20 + b21);
    let m12 = (-a02 + a21 + a22) * (b11 + b20 - b21);
    let m13 = (a02 - a22) * (b11 - b21);
    let m14 = a02 * b20;
    let m15 = (a21 + a22) * (-b20 + b21);
    let m16 = (-a02 + a11 + a12) * (b12 + b20 - b22);
    let m17 = (a02 - a12) * (b12 - b22);
    let m18 = (a11 + a12) * (-b20 + b22);
    let m19 = a01 * b10;
    let m20 = a12 * b21;
    let m21 = a10 * b02;
    let m22 = a20 * b01;
    let m23 = a22 * b22;

    let c00 = m6 + m14 + m19;
    let c01 = m1 + m4 + m5 + m6 + m12 + m14 + m15;
    let c02 = m6 + m7 + m9 + m10 + m14 + m16 + m18;
    let c10 = m2 + m3 + m4 + m6 + m14 + m16 + m17;
    let c11 = m2 + m4 + m5 + m6 + m20;
    let c12 = m14 + m16 + m17 + m18 + m21;
    let c20 = m6 + m7 + m8 + m11 + m12 + m13 + m14;
    let c21 = m12 + m13 + m14 + m15 + m22;
    let c22 = m6 + m7 + m8 + m9 + m23;

    result.set(0, 0, c00);
    result.set(0, 1, c01);
    result.set(0, MLINDEX_BYTES_NUM_2, c02);
    result.set(1, 0, c10);
    result.set(1, 1, c11);
    result.set(1, MLINDEX_BYTES_NUM_2, c12);
    result.set(MLINDEX_BYTES_NUM_2, 0, c20);
    result.set(MLINDEX_BYTES_NUM_2, 1, c21);
    result.set(MLINDEX_BYTES_NUM_2, MLINDEX_BYTES_NUM_2, c22);
    return result;
  }

  mmulStrassen(y: Matrix): Matrix {
    let x: Matrix = this.clone();
    let r1: number = x.rows;
    let c1: number = x.columns;
    let r2: number = y.rows;
    let c2: number = y.columns;
    if (c1 !== r2) {
      throw new RangeError(`Multiplying ${r1} x ${c1} and ${r2} x ${c2} matrix: dimensions do not match.`);
    }
    let r = Math.max(r1, r2);
    let c = Math.max(c1, c2);
    x = this.embed(x, r, c);
    y = this.embed(y, r, c);

    return this.blockMult(x, y, r, c);
  }

  embed(mat: Matrix, rows: number, cols: number): Matrix {
    let r: number = mat.rows;
    let c: number = mat.columns;
    if (r === rows && c === cols) {
      return mat;
    } else {
      let resultat = Matrix.zeros(rows, cols);
      resultat = resultat.setSubMatrix(mat, 0, 0);
      return resultat;
    }
  }

  blockMult(a: Matrix, b: Matrix, rows: number, cols: number): Matrix {
    let newA = a;
    let newB = b;
    if (rows <= MLINDEX_BYTES_NUM_512 || cols <= MLINDEX_BYTES_NUM_512) {
      return newA.mmul(newB);
    }
    if (rows % MLINDEX_BYTES_NUM_2 === 1 && cols % MLINDEX_BYTES_NUM_2 === 1) {
      newA = this.embed(newA, rows + 1, cols + 1);
      newB = this.embed(newB, rows + 1, cols + 1);
    } else if (rows % MLINDEX_BYTES_NUM_2 === 1) {
      newA = this.embed(newA, rows + 1, cols);
      newB = this.embed(newB, rows + 1, cols);
    } else if (cols % MLINDEX_BYTES_NUM_2 === 1) {
      newA = this.embed(newA, rows, cols + 1);
      newB = this.embed(newB, rows, cols + 1);
    }
    let halfRows = Number(newA.rows / MLINDEX_BYTES_NUM_2);
    let halfCols = Number(newA.columns / MLINDEX_BYTES_NUM_2);
    let a11 = newA.subMatrix(0, halfRows - 1, 0, halfCols - 1);
    let b11 = newA.subMatrix(0, halfRows - 1, 0, halfCols - 1);
    let a12 = newA.subMatrix(0, halfRows - 1, halfCols, newA.columns - 1);
    let b12 = newB.subMatrix(0, halfRows - 1, halfCols, newB.columns - 1);
    let a21 = newA.subMatrix(halfRows, newA.rows - 1, 0, halfCols - 1);
    let b21 = newB.subMatrix(halfRows, newB.rows - 1, 0, halfCols - 1);
    let a22 = newA.subMatrix(halfRows, newA.rows - 1, halfCols, newA.columns - 1);
    let b22 = newB.subMatrix(halfRows, newB.rows - 1, halfCols, newB.columns - 1);
    let m1 = this.blockMult(Matrix.add(a11, a22), Matrix.add(b11, b22), halfRows, halfCols);
    let m2 = this.blockMult(Matrix.add(a21, a22), b11, halfRows, halfCols);
    let m3 = this.blockMult(a11, Matrix.sub(b12, b22), halfRows, halfCols);
    let m4 = this.blockMult(a22, Matrix.sub(b21, b11), halfRows, halfCols);
    let m5 = this.blockMult(Matrix.add(a11, a12), b22, halfRows, halfCols);
    let m6 = this.blockMult(Matrix.sub(a21, a11), Matrix.add(b11, b12), halfRows, halfCols);
    let m7 = this.blockMult(Matrix.sub(a12, a22), Matrix.add(b21, b22), halfRows, halfCols);
    let c11 = Matrix.add(m1, m4);
    c11.sub(m5);
    c11.add(m7);
    let c12 = Matrix.add(m3, m5);
    let c21 = Matrix.add(m2, m4);
    let c22 = Matrix.sub(m1, m2);
    c22.add(m3);
    c22.add(m6);
    let resultat = Matrix.zeros(MLINDEX_BYTES_NUM_2 * c11.rows, MLINDEX_BYTES_NUM_2 * c11.columns);
    resultat = resultat.setSubMatrix(c11, 0, 0);
    resultat = resultat.setSubMatrix(c12, c11.rows, 0);
    resultat = resultat.setSubMatrix(c21, 0, c11.columns);
    resultat = resultat.setSubMatrix(c22, c11.rows, c11.columns);
    return resultat.subMatrix(0, rows - 1, 0, cols - 1);
  }

  scaleRows(min: number, max: number): Matrix {
    if (min >= max) {
      throw new RangeError('min should be strictly smaller than max');
    }
    let options: MlOptions = new MlOptions();
    options.min = min;
    options.max = max;
    let newMatrix = new Matrix(this.rows, this.columns);
    for (let i = 0; i < this.rows; i++) {
      let scaled: number | number[] | number[][] | Matrix = mlArrayUtilsArrayUtils.scale(this.getRow(i), options);
      newMatrix.setRow(i, scaled as Matrix);
    }
    return newMatrix;
  }

  scaleColumns(min: number, max: number): Matrix {
    if (min >= max) {
      throw new RangeError('min should be strictly smaller than max');
    }
    let newMatrix = Matrix.empty(this.rows, this.columns);
    let options: MlOptions = new MlOptions();
    options.min = min;
    options.max = max;
    for (let i = 0; i < this.columns; i++) {
      let scaled: number | number[] | number[][] | Matrix = mlArrayUtilsArrayUtils.scale(this.getColumn(i), options);
      newMatrix.setColumn(i, scaled as number[]);
    }
    return newMatrix;
  }

  kroneckerProduct(other: Matrix): Matrix {
    other = Matrix.checkMatrix(other);

    let m: number = this.rows;
    let n: number = this.columns;
    let p: number = other.rows;
    let q: number = other.columns;

    let result: Matrix = new Matrix(m * p, n * q);
    for (let i = 0; i < m; i++) {
      for (let j = 0; j < n; j++) {
        for (let k = 0; k < p; k++) {
          for (let l = 0; l < q; l++) {
            result.set(p * i + k, q * j + l, this.get(i, j) * other.get(k, l));
          }
        }
      }
    }
    return result;
  }

  transpose(): Matrix {
    let result = new Matrix(this.columns, this.rows);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        result.set(j, i, this.get(i, j));
      }
    }
    return result;
  }

  sortRows(compareFunction: () => void): Matrix {
    return this;
  }

  sortColumns(compareFunction: () => void): Matrix {
    return this;
  }

  subMatrix(startRow: number, endRow: number, startColumn: number, endColumn: number): Matrix {
    mlMatrixUtil.checkRange(this, startRow, endRow, startColumn, endColumn);
    let newMatrix = new Matrix(endRow - startRow + 1, endColumn - startColumn + 1);
    for (let i = startRow; i <= endRow; i++) {
      for (let j = startColumn; j <= endColumn; j++) {
        newMatrix.set(i - startRow, j - startColumn, this.get(i, j));
      }
    }
    return newMatrix;
  }

  subMatrixRow(indices: number[], startColumn: number, endColumn?: number): Matrix {
    let newEndColumn = endColumn;
    if (newEndColumn === undefined) {
      newEndColumn = this.columns - 1;
    }
    if (startColumn > newEndColumn || startColumn < 0 || startColumn >= this.columns || newEndColumn < 0 || newEndColumn >= this.columns) {
      throw new RangeError('Argument out of range');
    }

    let newMatrix = new Matrix(indices.length, newEndColumn - startColumn + 1);
    for (let i = 0; i < indices.length; i++) {
      for (let j = startColumn; j <= newEndColumn; j++) {
        if (indices[i] < 0 || indices[i] >= this.rows) {
          throw new RangeError('Row index out of range: ' + indices[i]);
        }
        newMatrix.set(i, j - startColumn, this.get(indices[i], j));
      }
    }
    return newMatrix;
  }

  subMatrixColumn(indices: number[], startRow: number, endRow?: number): Matrix {
    let newEndRow = endRow;
    if (newEndRow === undefined) {
      newEndRow = this.rows - 1;
    }
    if (startRow > endRow! || startRow < 0 || startRow >= this.rows || newEndRow! < 0 || newEndRow! >= this.rows) {
      throw new RangeError('Argument out of range');
    }

    let newMatrix = new Matrix(newEndRow! - startRow + 1, indices.length);
    for (let i = 0; i < indices.length; i++) {
      for (let j = startRow; j <= newEndRow; j++) {
        if (indices[i] < 0 || indices[i] >= this.columns) {
          throw new RangeError('Column index out of range: ' + indices[i]);
        }
        newMatrix.set(j - startRow, i, this.get(j, indices[i]));
      }
    }
    return newMatrix;
  }

  setSubMatrix(matrix: Matrix, startRow: number, startColumn: number): Matrix {
    matrix = Matrix.checkMatrix(matrix);
    let endRow = startRow + matrix.rows - 1;
    let endColumn = startColumn + matrix.columns - 1;
    mlMatrixUtil.checkRange(this, startRow, endRow, startColumn, endColumn);
    for (let i = 0; i < matrix.rows; i++) {
      for (let j = 0; j < matrix.columns; j++) {
        matrix.set(startRow + i, startColumn + j, matrix.get(i, j));
      }
    }
    return this;
  }

  selection(rowIndices: number[], columnIndices: number[]): Matrix {
    mlMatrixUtil.checkIndices(this, rowIndices, columnIndices);
    let newMatrix = new Matrix(rowIndices.length, columnIndices.length);
    return newMatrix;
  }

  trace(): number {
    let min = Math.min(this.rows, this.columns);
    let trace = 0.0;
    for (let i = 0; i < min; i++) {
      trace += this.get(i, i);
    }
    return trace;
  }

  transposeView(): MlMatrixTransposeView {
    return new MlMatrixTransposeView(this);
  }

  rowView(row: number): MlMatrixRowView {
    mlMatrixUtil.checkRowIndex(this, row, false);
    return new MlMatrixRowView(this, row);
  }

  columnView(column: number): MlMatrixColumnView {
    mlMatrixUtil.checkColumnIndex(this, column, false);
    return new MlMatrixColumnView(this, column);
  }

  flipRowView(): MlMatrixFlipRowView {
    return new MlMatrixFlipRowView(this);
  }

  flipColumnView(): MlMatrixFlipColumnView {
    return new MlMatrixFlipColumnView(this);
  }

  subMatrixView(startRow: number, endRow: number, startColumn: number, endColumn: number): MlMatrixSubView {
    return new MlMatrixSubView(this, startRow, endRow, startColumn, endColumn);
  }

  selectionView(rowIndices: number[], columnIndices: number[]): MlMatrixSelectionView {
    return new MlMatrixSelectionView(this, rowIndices, columnIndices);
  }

  det(): number {
    if (this.isSquare()) {
      let a: number;
      let b: number;
      let c: number;
      let d: number;
      if (this.columns === MLINDEX_BYTES_NUM_2) {
        a = this.get(0, 0);
        b = this.get(0, 1);
        c = this.get(1, 0);
        d = this.get(1, 1);

        return a * d - b * c;
      } else if (this.columns === MLINDEX_BYTES_NUM_3) {
        let subMatrix0: MlMatrixSelectionView;
        let subMatrix1: MlMatrixSelectionView;
        let subMatrix2: MlMatrixSelectionView;
        subMatrix0 = this.selectionView([1, MLINDEX_BYTES_NUM_2], [1, MLINDEX_BYTES_NUM_2]);
        subMatrix1 = this.selectionView([1, MLINDEX_BYTES_NUM_2], [0, MLINDEX_BYTES_NUM_2]);
        subMatrix2 = this.selectionView([1, MLINDEX_BYTES_NUM_2], [0, 1]);
        a = this.get(0, 0);
        b = this.get(0, 1);
        c = this.get(0, MLINDEX_BYTES_NUM_2);
        return a * subMatrix0.det() - b * subMatrix1.det() + c * subMatrix2.det();
      } else {
        // general purpose determinant using the LU decomposition
        return new MlMatrixDclu(this).determinant();
      }
    } else {
      throw Error('Determinant can only be calculated for a square matrix.');
    }
  }

  checkDimensions(matrix: Matrix, otherMatrix: Matrix): void {
    if (matrix.rows !== otherMatrix.rows || matrix.columns !== otherMatrix.columns) {
      throw new RangeError('Matrices dimensions must be equal');
    }
  }

  compareNumbers(a: number, b: number): number {
    return a - b;
  }

  static inverse: (matrix: Matrix) => Matrix = (matrix: Matrix): Matrix => {
    return mlMatrixDecompositions.inverse(matrix);
  };

  static inv: (matrix: Matrix) => Matrix = (matrix: Matrix): Matrix => {
    return mlMatrixDecompositions.inverse(matrix);
  };

  inverse(): Matrix {
    return mlMatrixDecompositions.inverse(this);
  }

  inv(): Matrix {
    return this.inverse();
  }

  solve(other: Matrix): Matrix {
    return mlMatrixDecompositions.solve(this, other);
  }
  add(value: number | number[] | number[][] | Matrix): Matrix {
    if (typeof value === 'number') {
      return this.addS(value);
    }
    return this.addM(value as Matrix);
  }

  addS(value: number): Matrix {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) + value);
      }
    }
    return this;
  }

  addM(matrix: Matrix): Matrix {
    matrix = Matrix.checkMatrix(matrix);
    this.checkDimensions(this, matrix);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) + matrix.get(i, j));
      }
    }
    return this;
  }

  static add(matrix: Matrix, value: number | number[] | number[][] | Matrix): Matrix {
    let newMatrix: Matrix = new Matrix(matrix);
    return newMatrix.add(value);
  }

  sub(value: number | number[] | number[][] | Matrix): Matrix {
    if (typeof value === 'number') {
      return this.subS(value);
    }
    return this.subM(value as Matrix);
  }

  subS(value: number): Matrix {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) - value);
      }
    }
    return this;
  }

  subM(matrix: Matrix): Matrix {
    matrix = Matrix.checkMatrix(matrix);
    this.checkDimensions(this, matrix);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) - matrix.get(i, j));
      }
    }
    return this;
  }

  static sub(matrix: Matrix, value: number | number[] | number[][] | Matrix): Matrix {
    let newMatrix = new Matrix(matrix);
    return newMatrix.sub(value);
  }

  mul(value: number | number[] | number[][] | Matrix): Matrix {
    if (typeof value === 'number') {
      return this.mulS(value);
    }
    return this.mulM(value as Matrix);
  }

  mulS(value: number): Matrix {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) * value);
      }
    }
    return this;
  }

  mulM(matrix: Matrix): Matrix {
    matrix = Matrix.checkMatrix(matrix);
    this.checkDimensions(this, matrix);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) * matrix.get(i, j));
      }
    }
    return this;
  }

  static mul(matrix: number, value: number | number[] | number[][] | Matrix): Matrix {
    let newMatrix = new Matrix(matrix);
    return newMatrix.mul(value);
  }

  div(value: number | number[] | number[][] | Matrix): Matrix {
    if (typeof value === 'number') {
      return this.divS(value);
    }
    return this.divM(value as Matrix);
  }

  divS(value: number): Matrix {
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) / value);
      }
    }
    return this;
  }

  divM(matrix: Matrix): Matrix {
    matrix = Matrix.checkMatrix(matrix);
    this.checkDimensions(this, matrix);
    for (let i = 0; i < this.rows; i++) {
      for (let j = 0; j < this.columns; j++) {
        this.set(i, j, this.get(i, j) / matrix.get(i, j));
      }
    }
    return this;
  }

  static div(matrix: number, value: number | number[] | number[][] | Matrix): Matrix {
    let newMatrix = new Matrix(matrix);
    return newMatrix.div(value);
  }

  removeRow(index: number): Matrix {
    mlMatrixUtil.checkRowIndex(this, index, false);
    if (this.rows === 1) {
      throw new RangeError('A matrix cannot have less than one row');
    }
    this.rows -= 1;
    return this;
  }

  addRow(index: number, array: number[]): Matrix {
    mlMatrixUtil.checkRowIndex(this, index, true);
    mlMatrixUtil.checkRowVector(this, array);
    this.rows += 1;
    return this;
  }

  removeColumn(index: number): Matrix {
    mlMatrixUtil.checkColumnIndex(this, index, false);
    if (this.columns === 1) {
      throw new RangeError('A matrix cannot have less than one column');
    }
    this.columns -= 1;
    return this;
  }

  addColumn(index: number, array: number[]): Matrix {
    let newArray = array;
    mlMatrixUtil.checkColumnIndex(this, index, true);
    newArray = mlMatrixUtil.checkColumnVector(this, newArray) as [];
    this.columns += 1;
    return this;
  }
}

class MlArrayUtilsArrayUtilsCls {
  coordArrayToPoints(array: Array<number>, dimensions: number): number[][] {
    if (array.length % dimensions !== 0) {
      throw new RangeError('Dimensions number must be accordance with the size of the array.');
    }
    let length = array.length / dimensions;
    let pointsArr: number[][] = Array(length).fill(null);
    let k = 0;
    let i = 0;
    for (i = 0; i < array.length; i += dimensions) {
      let point: number[] = Array(dimensions).fill(null);
      for (let j = 0; j < dimensions; ++j) {
        point[j] = array[i + j];
      }
      pointsArr[k] = point;
      k += 1;
      i += dimensions;
    }
    return pointsArr;
  }

  coordArrayToCoordMatrix(array: number[], dimensions: number): number[][] {
    if (array.length % dimensions !== 0) {
      throw new RangeError('Dimensions number must be accordance with the size of the array.');
    }
    let coordinatesArray: number[][] = Array(dimensions).fill([0]);
    let points = array.length / dimensions;
    for (let i = 0; i < coordinatesArray.length; i++) {
      coordinatesArray[i] = Array(points).fill(0);
    }
    for (let i = 0; i < array.length; i += dimensions) {
      for (let j = 0; j < dimensions; ++j) {
        let currentPoint = Math.floor(i / dimensions);
        coordinatesArray[j][currentPoint] = array[i + j];
      }
      i += dimensions;
    }
    return coordinatesArray;
  }

  coordMatrixToCoordArray(coordMatrix: number[][]): number[] {
    let coodinatesArray: number[] = Array(coordMatrix.length * coordMatrix[0].length).fill(0);
    let k = 0;
    for (let i = 0; i < coordMatrix[0].length; ++i) {
      for (let j = 0; j < coordMatrix.length; ++j) {
        coodinatesArray[k] = coordMatrix[j][i];
        k += 1;
      }
    }
    return coodinatesArray;
  }

  transpose(matrix: number[][]): number[][] {
    let resultMatrix: number[][] = Array(matrix[0].length).fill([0]);
    for (let i = 0; i < resultMatrix.length; ++i) {
      resultMatrix[i] = Array(matrix.length).fill(0);
    }

    for (let i = 0; i < matrix.length; ++i) {
      for (let j = 0; j < matrix[i].length; ++j) {
        resultMatrix[j][i] = matrix[i][j];
      }
    }

    return resultMatrix;
  }

  pointsToCoordArray(points: number[][]): number[] {
    let coodinatesArray: number[] = Array(points.length * points[0].length).fill(0);
    let k = 0;
    for (let i = 0; i < points.length; ++i) {
      for (let j = 0; j < points[0].length; ++j) {
        coodinatesArray[k] = points[i][j];
        k += 1;
      }
    }
    return coodinatesArray;
  }

  applyDotProduct(firstVector: number[], secondVector: number[]): number[] {
    let largestVector: number[];
    let smallestVector: number[];
    if (firstVector.length <= secondVector.length) {
      smallestVector = firstVector;
      largestVector = secondVector;
    } else {
      smallestVector = secondVector;
      largestVector = firstVector;
    }
    let difference = largestVector.length - smallestVector.length + 1;
    let dotProductApplied: number[] = Array(difference).fill(0);
    for (let i = 0; i < difference; ++i) {
      let sum = 0;
      for (let j = 0; j < smallestVector.length; ++j) {
        sum += smallestVector[j] * largestVector[i + j];
      }
      dotProductApplied[i] = sum;
    }
    return dotProductApplied;
  }

  scale(input: number[], options: MlOptions): number | number[] | number[][] | Matrix {
    let y: number[];
    if (options.inPlace) {
      y = input;
    } else {
      y = Array(input.length).fill(0.0);
    }
    return y;
  }
}

let mlArrayUtilsArrayUtils = new MlArrayUtilsArrayUtilsCls();

// ml-matrix src/views/base
class MlMatrixBaseView extends Matrix {
  matrix: Matrix;

  constructor(matrix: Matrix, rows: number, columns: number) {
    super(matrix);
    this.matrix = matrix;
    this.rows = rows;
    this.columns = columns;
  }
}

// ml-matrix src/views/column.js

class MlMatrixColumnView extends MlMatrixBaseView {
  column: number;

  constructor(matrix: Matrix, column: number) {
    super(matrix, matrix.rows, 1);
    this.column = column;
  }

  set(rowIndex: number, columnIndex: number, value: number): MlMatrixColumnView {
    this.matrix.set(rowIndex, this.column, value);
    return this;
  }

  get(rowIndex: number): number {
    return this.matrix.get(rowIndex, this.column);
  }
}

// ml-matrix src/views/flipColumn.js

class MlMatrixFlipColumnView extends MlMatrixBaseView {
  constructor(matrix: Matrix) {
    super(matrix, matrix.rows, matrix.columns);
  }

  set(rowIndex: number, columnIndex: number, value: number): MlMatrixFlipColumnView {
    this.matrix.set(rowIndex, this.columns - columnIndex - 1, value);
    return this;
  }

  get(rowIndex: number, columnIndex: number): number {
    return this.matrix.get(rowIndex, this.columns - columnIndex - 1);
  }
}

// ml-matrix src/views/flipRow.js

class MlMatrixFlipRowView extends MlMatrixBaseView {
  constructor(matrix: Matrix) {
    super(matrix, 1, matrix.columns);
  }

  set(rowIndex: number, columnIndex: number, value: number): MlMatrixFlipRowView {
    this.matrix.set(this.rows - rowIndex - 1, columnIndex, value);
    return this;
  }

  get(rowIndex: number, columnIndex: number): number {
    return this.matrix.get(this.rows - rowIndex - 1, columnIndex);
  }
}

// ml-matrix src/views/row.js
class MlMatrixRowView extends MlMatrixBaseView {
  row: number;

  constructor(matrix: Matrix, row: number) {
    super(matrix, 1, matrix.columns);
    this.row = row;
  }

  set(rowIndex: number, columnIndex: number, value: number): MlMatrixRowView {
    this.matrix.set(this.row, columnIndex, value);
    return this;
  }

  get(rowIndex: number, columnIndex: number): number {
    return this.matrix.get(this.row, columnIndex);
  }
}

// ml-matrix src/views/selection.js

class MlMatrixSelectionView extends MlMatrixBaseView {
  rowIndices: number[];
  columnIndices: number[];

  constructor(matrix: Matrix, rowIndices: number[], columnIndices: number[]) {
    super(matrix, 0, 0);
    let indices: [row: number[], column: number[]] = mlMatrixUtil.checkIndices(matrix, rowIndices, columnIndices);
    this.rowIndices = indices[0];
    this.columnIndices = indices[1];
  }

  set(rowIndex: number, columnIndex: number, value: number): MlMatrixSelectionView {
    this.matrix.set(this.rowIndices[rowIndex], this.columnIndices[columnIndex], value);
    return this;
  }

  get(rowIndex: number, columnIndex: number): number {
    return this.matrix.get(this.rowIndices[rowIndex], this.columnIndices[columnIndex]);
  }
}

// ml-matrix src/views/sub.js

class MlMatrixSubView extends MlMatrixBaseView {
  startRow: number;
  startColumn: number;

  constructor(matrix: Matrix, startRow: number, endRow: number, startColumn: number, endColumn: number) {
    super(matrix, endRow - startRow + 1, endColumn - startColumn + 1);
    mlMatrixUtil.checkRange(matrix, startRow, endRow, startColumn, endColumn);
    this.startRow = startRow;
    this.startColumn = startColumn;
  }

  set(rowIndex: number, columnIndex: number, value: number): MlMatrixSubView {
    this.matrix.set(this.startRow + rowIndex, this.startColumn + columnIndex, value);
    return this;
  }

  get(rowIndex: number, columnIndex: number): number {
    return this.matrix.get(this.startRow + rowIndex, this.startColumn + columnIndex);
  }
}

// ml-matrix src/views/transpose.js

class MlMatrixTransposeView extends MlMatrixBaseView {
  constructor(matrix: Matrix) {
    super(matrix, matrix.columns, matrix.rows);
  }

  set(rowIndex: number, columnIndex: number, value: number): MlMatrixTransposeView {
    this.matrix.set(columnIndex, rowIndex, value);
    return this;
  }

  get(rowIndex: number, columnIndex: number): number {
    return this.matrix.get(columnIndex, rowIndex);
  }
}

// ml-matrix src/dc/cholesky.js
class MlMatrixDcCholesky {
  l: Matrix;

  constructor(value: Matrix) {
    value = Matrix.checkMatrix(value);

    if (!(value as Matrix).isSymmetric()) {
      throw new Error('Matrix is not symmetric');
    }
    let a: Matrix = value;
    let dimension: number = a.rows;
    let l: Matrix = new Matrix(dimension, dimension);
    let positiveDefinite: boolean = true;

    for (let j = 0; j < dimension; j++) {
      let lRowj: number[] = l.getRow(j);
      let d = 0.0;
      for (let k = 0; k < j; k++) {
        let lRowk: number[] = l.getRow(k);
        let s = 0.0;
        for (let i = 0; i < k; i++) {
          s += lRowk[i] * lRowj[i];
        }
        s = (a.get(j, k) - s) / l.get(k, k);
        lRowj[k] = s;
        d = d + s * s;
      }
      d = a.get(j, j) - d;
      if (d > 0) {
        positiveDefinite = true;
      } else {
        positiveDefinite = false;
      }
      l.set(j, j, Math.sqrt(Math.max(d, 0)));
      for (let k = j + 1; k < dimension; k++) {
        l.set(j, k, 0);
      }
    }
    if (!positiveDefinite) {
      throw new Error('Matrix is not positive definite');
    }
    this.l = l;
  }

  lowerTriangularMatrix(): number | number[] | number[][] | Matrix {
    return this.l;
  }

  solve(value: number | number[][] | Matrix): number | number[] | number[][] | Matrix {
    value = Matrix.checkMatrix(value);
    let l = this.l;
    let dimension: number = l.rows;

    if ((value as Matrix).rows !== dimension) {
      throw new Error('Matrix dimensions do not match');
    }

    let count: number = (value as Matrix).columns;
    let b: Matrix = (value as Matrix).clone();

    for (let k = 0; k < dimension; k++) {
      for (let j = 0; j < count; j++) {
        for (let i = 0; i < k; i++) {
          b.set(k, j, b.get(k, j) - b.get(i, j) * l.get(k, i));
        }
        b.set(k, j, b.get(k, j) / l.get(k, k));
      }
    }
    for (let k = dimension - 1; k >= 0; k--) {
      for (let j = 0; j < count; j++) {
        for (let i = k + 1; i < dimension; i++) {
          b.set(k, j, b.get(k, j) - b.get(i, j) * l.get(i, k));
        }
        b.set(k, j, b.get(k, j) / l.get(k, k));
      }
    }
    return b;
  }
}

// ml-matrix src/dc/evd.js
class MlMatrixDcevd {
  n: number = 0;
  e: number[] = [];
  d: number[] = [];
  v: number[][] = [[]];
  m1?: Matrix;

  constructor(matrix: Matrix, options: MlOptions) {
    matrix = Matrix.checkMatrix(matrix!);
    if (!matrix.isSquare()) {
      throw new Error('Matrix is not a square matrix');
    }
    let n: number = matrix.columns;
    let v: number[][] = mlMatrixDcuTil.getFilled2dArray(n, n, 0);
    let d: number[] = Array(n).fill(0.0);
    let e: number[] = Array(n).fill(0.0);
    let value: Matrix = matrix;

    let isSymmetric = false;
    if (options.defaultOptions.get('assumeSymmetric')) {
      isSymmetric = true;
    } else {
      isSymmetric = matrix.isSymmetric();
    }
    if (isSymmetric) {
      for (let i = 0; i < n; i++) {
        for (let j = 0; j < n; j++) {
          v[i][j] = value.get(i, j);
        }
      }
      this.tred2(n, e, d, v);
      this.tql2(n, e, d, v);
    } else {
      let h: number[][] = mlMatrixDcuTil.getFilled2dArray(n, n, 0);
      let ort: number[] = Array(n).fill(0.0);
      for (let j = 0; j < n; j++) {
        for (let i = 0; i < n; i++) {
          h[i][j] = value.get(i, j);
        }
      }
      this.orthes(n, h, ort, v);
      this.hqr2(n, e, d, v, h);
    }
    this.n = n;
    this.e = e;
    this.d = d;
    this.v = v;
  }

  realEigenvalues(): number[] {
    return this.d;
  }

  imaginaryEigenvalues(): number[] {
    return this.e;
  }

  eigenvectorMatrix(): Matrix {
    if (!Matrix.isMatrix(this.v)) {
      this.m1 = new Matrix(this.v);
    }
    return this.m1!;
  }

  diagonalMatrix(): Matrix {
    let n: number = this.n;
    let e: number[] = this.e;
    let d: number[] = this.d;
    let x: Matrix = new Matrix(n, n);

    for (let i = 0; i < n; i++) {
      for (let j = 0; j < n; j++) {
        x.set(i, j, 0);
      }
      x.set(i, i, d[i]);
      if (e[i] > 0) {
        x.set(i, i + 1, e[i]);
      } else if (e[i] < 0) {
        x.set(i, i - 1, e[i]);
      }
    }
    return x;
  }

  tred2(n: number, e: number[], d: number[], v: number[][]): void {
    let f: number;
    let g: number;
    let h: number;
    let hh: number;
    let scale: number;
    let ei = e;
    let di = d;
    let vi = v;

    for (let j = 0; j < n; j++) {
      di[j] = vi[n - 1][j];
    }

    for (let i = n - 1; i > 0; i--) {
      scale = 0;
      h = 0;
      for (let k = 0; k < i; k++) {
        scale = scale + Math.abs(di[k]);
      }

      if (scale === 0) {
        ei[i] = di[i - 1];
        for (let j = 0; j < i; j++) {
          di[j] = vi[i - 1][j];
          vi[i][j] = 0;
        }
      } else {
        for (let k = 0; k < i; k++) {
          di[k] /= scale;
          h += di[k] * di[k];
        }

        f = di[i - 1];
        g = Math.sqrt(h);
        if (f > 0) {
          g = -g;
        }

        ei[i] = scale * g;
        h = h - f * g;
        di[i - 1] = f - g;
        for (let j = 0; j < i; j++) {
          ei[j] = 0;
        }

        for (let j = 0; j < i; j++) {
          f = di[j];
          vi[j][i] = f;
          g = ei[j] + vi[j][j] * f;
          for (let k = j + 1; k <= i - 1; k++) {
            g += vi[k][j] * di[k];
            ei[k] += vi[k][j] * f;
          }
          ei[j] = g;
        }

        f = 0;
        for (let j = 0; j < i; j++) {
          ei[j] /= h;
          f += ei[j] * di[j];
        }

        hh = f / (h + h);
        for (let j = 0; j < i; j++) {
          ei[j] -= hh * di[j];
        }

        for (let j = 0; j < i; j++) {
          f = di[j];
          g = ei[j];
          for (let k = j; k <= i - 1; k++) {
            vi[k][j] -= f * ei[k] + g * di[k];
          }
          di[j] = vi[i - 1][j];
          vi[i][j] = 0;
        }
      }
      di[i] = h;
    }

    for (let i = 0; i < n - 1; i++) {
      vi[n - 1][i] = vi[i][i];
      vi[i][i] = 1;
      h = di[i + 1];
      if (h !== 0) {
        for (let k = 0; k <= i; k++) {
          di[k] = vi[k][i + 1] / h;
        }
        for (let j = 0; j <= i; j++) {
          g = 0;
          for (let k = 0; k <= i; k++) {
            g += vi[k][i + 1] * vi[k][j];
          }
          for (let k = 0; k <= i; k++) {
            vi[k][j] -= g * di[k];
          }
        }
      }

      for (let k = 0; k <= i; k++) {
        vi[k][i + 1] = 0;
      }
    }

    for (let j = 0; j < n; j++) {
      di[j] = vi[n - 1][j];
      vi[n - 1][j] = 0;
    }

    vi[n - 1][n - 1] = 1;
    ei[0] = 0;
  }

  tql2(n: number, e: number[], d: number[], v: number[][]): void {
    let g: number;
    let h: number;
    let k: number;
    let m: number;
    let p: number;
    let r: number;
    let dl1: number;
    let c: number;
    let c2: number;
    let c3: number;
    let el1: number;
    let s: number;
    let s2: number;
    let iter: number;
    let ei = e;
    let di = d;
    let vi = v;

    for (let i = 1; i < n; i++) {
      ei[i - 1] = ei[i];
    }

    ei[n - 1] = 0;

    let f = 0.0;
    let tst1 = 0.0;
    let eps = Math.pow(MLINDEX_BYTES_NUM_20, MLINDEX_BYTES_NUM_0520);

    for (let l = 0; l < n; l++) {
      tst1 = Math.max(tst1, Math.abs(di[l]) + Math.abs(ei[l]));
      m = l;
      while (m < n) {
        if (Math.abs(ei[m]) <= eps * tst1) {
          break;
        }
        m += 1;
      }

      if (m > l) {
        iter = 0;
        do {
          iter = iter + 1;

          g = di[l];
          p = (di[l + 1] - g) / (MLINDEX_BYTES_NUM_2 * ei[l]);
          r = mlMatrixDcuTil.hypotenuse(p, 1);
          if (p < 0) {
            r = -r;
          }

          di[l] = ei[l] / (p + r);
          di[l + 1] = ei[l] * (p + r);
          dl1 = di[l + 1];
          h = g - di[l];
          for (let i = l + MLINDEX_BYTES_NUM_2; i < n; i++) {
            di[i] -= h;
          }

          f = f + h;
          p = di[m];
          c = 1;
          c2 = c;
          c3 = c;
          el1 = ei[l + 1];
          s = 0;
          s2 = 0;
          for (let i = m - 1; i >= l; i--) {
            c3 = c2;
            c2 = c;
            s2 = s;
            g = c * ei[i];
            h = c * p;
            r = mlMatrixDcuTil.hypotenuse(p, ei[i]);
            ei[i + 1] = s * r;
            s = ei[i] / r;
            c = p / r;
            p = c * di[i] - s * g;
            di[i + 1] = h + s * (c * g + s * di[i]);
            for (k = 0; k < n; k++) {
              h = vi[k][i + 1];
              vi[k][i + 1] = s * vi[k][i] + c * h;
              vi[k][i] = c * vi[k][i] - s * h;
            }
          }

          p = (-s * s2 * c3 * el1 * ei[l]) / dl1;
          ei[l] = s * p;
          di[l] = c * p;
        } while (Math.abs(ei[l]) > eps * tst1);
      }
      di[l] = di[l] + f;
      ei[l] = 0;
    }

    for (let i = 0; i < n - 1; i++) {
      k = i;
      p = di[i];
      for (let j = i + 1; j < n; j++) {
        if (di[j] < p) {
          k = j;
          p = di[j];
        }
      }

      if (k !== i) {
        di[k] = di[i];
        di[i] = p;
        for (let j = 0; j < n; j++) {
          p = vi[j][i];
          vi[j][i] = vi[j][k];
          vi[j][k] = p;
        }
      }
    }
  }

  orthes(n: number, h: number[][], ort: number[], v: number[][]): void {
    let low: number = 0;
    let high: number = n - 1;
    let f: number;
    let g: number;
    let hl: number;
    let scale: number;
    let hi = h;
    let orti = ort;
    let vi = v;

    for (let m = low + 1; m <= high - 1; m++) {
      scale = 0;
      for (let i = m; i <= high; i++) {
        scale = scale + Math.abs(hi[i][m - 1]);
      }

      if (scale !== 0) {
        hl = 0;
        for (let i = high; i >= m; i--) {
          orti[i] = hi[i][m - 1] / scale;
          hl += orti[i] * orti[i];
        }

        g = Math.sqrt(Number(h));
        if (orti[m] > 0) {
          g = -g;
        }

        hl = hl - orti[m] * g;
        orti[m] = orti[m] - g;

        for (let j = m; j < n; j++) {
          f = 0;
          for (let i = high; i >= m; i--) {
            f += orti[i] * hi[i][j];
          }

          f = f / hl;
          for (let i = m; i <= high; i++) {
            hi[i][j] -= f * orti[i];
          }
        }

        for (let i = 0; i <= high; i++) {
          f = 0;
          for (let j = high; j >= m; j--) {
            f += orti[j] * hi[i][j];
          }

          f = f / hl;
          for (let j = m; j <= high; j++) {
            hi[i][j] -= f * orti[j];
          }
        }

        orti[m] = scale * orti[m];
        hi[m][m - 1] = scale * g;
      }
    }

    for (let i = 0; i < n; i++) {
      for (let j = 0; j < n; j++) {
        vi[i][j] = i === j ? 1 : 0;
      }
    }

    for (let m = high - 1; m >= low + 1; m--) {
      if (hi[m][m - 1] !== 0) {
        for (let i = m + 1; i <= high; i++) {
          orti[i] = hi[i][m - 1];
        }

        for (let j = m; j <= high; j++) {
          g = 0.0;
          for (let i = m; i <= high; i++) {
            g += orti[i] * vi[i][j];
          }

          g = g / orti[m] / hi[m][m - 1];
          for (let i = m; i <= high; i++) {
            vi[i][j] += g * orti[i];
          }
        }
      }
    }
  }

  hqr2(nn: number, e: number[], d: number[], v: number[][], h: number[][]): void {
    let n: number = nn - 1;
    let low: number = 0;
    let high: number = nn - 1;
    let eps: number = Math.pow(MLINDEX_BYTES_NUM_20, MLINDEX_BYTES_NUM_0520);
    let exshift: number = 0.0;
    let norm: number = 0.0;
    let p: number = 0.0;
    let q: number = 0.0;
    let r: number = 0.0;
    let s: number = 0.0;
    let z: number = 0.0;
    let iter: number = 0.0;
    let l: number;
    let m: number;
    let t: number;
    let w: number;
    let x: number;
    let y: number;
    let ra: number;
    let sa: number;
    let vr: number;
    let vi: number;
    let notlast: boolean;
    let cdivres: number[];

    let nni = nn;
    let ei = e;
    let di = d;
    let vd = v;
    let hi = h;

    for (let i = 0; i < nni; i++) {
      if (i < low || i > high) {
        di[i] = hi[i][i];
        ei[i] = 0;
      }

      for (let j = Math.max(i - 1, 0); j < nni; j++) {
        norm = norm + Math.abs(hi[i][j]);
      }
    }

    while (n >= low) {
      l = n;
      while (l > low) {
        s = Math.abs(hi[l - 1][l - 1]) + Math.abs(hi[l][l]);
        if (s === 0) {
          s = norm;
        }
        if (Math.abs(hi[l][l - 1]) < eps * s) {
          break;
        }
        l -= 1;
      }

      if (l === n) {
        hi[n][n] = hi[n][n] + exshift;
        di[n] = hi[n][n];
        ei[n] = 0;
        n -= 1;
        iter = 0;
      } else if (l === n - 1) {
        w = hi[n][n - 1] * hi[n - 1][n];
        p = (hi[n - 1][n - 1] - hi[n][n]) / MLINDEX_BYTES_NUM_2;
        q = p * p + w;
        z = Math.sqrt(Math.abs(q));
        hi[n][n] = hi[n][n] + exshift;
        hi[n - 1][n - 1] = hi[n - 1][n - 1] + exshift;
        x = hi[n][n];

        if (q >= 0) {
          z = p >= 0 ? p + z : p - z;
          di[n - 1] = x + z;
          di[n] = di[n - 1];
          if (z !== 0) {
            di[n] = x - w / z;
          }
          ei[n - 1] = 0;
          ei[n] = 0;
          x = hi[n][n - 1];
          s = Math.abs(x) + Math.abs(z);
          p = x / s;
          q = z / s;
          r = Math.sqrt(p * p + q * q);
          p = p / r;
          q = q / r;

          for (let j = n - 1; j < nni; j++) {
            z = hi[n - 1][j];
            hi[n - 1][j] = q * z + p * hi[n][j];
            hi[n][j] = q * hi[n][j] - p * z;
          }

          for (let i = 0; i <= n; i++) {
            z = hi[i][n - 1];
            hi[i][n - 1] = q * z + p * hi[i][n];
            hi[i][n] = q * hi[i][n] - p * z;
          }

          for (let i = low; i <= high; i++) {
            z = vd[i][n - 1];
            vd[i][n - 1] = q * z + p * vd[i][n];
            vd[i][n] = q * vd[i][n] - p * z;
          }
        } else {
          di[n - 1] = x + p;
          di[n] = x + p;
          ei[n - 1] = z;
          ei[n] = -z;
        }

        n = n - MLINDEX_BYTES_NUM_2;
        iter = 0;
      } else {
        x = hi[n][n];
        y = 0;
        w = 0;
        if (l < n) {
          y = hi[n - 1][n - 1];
          w = hi[n][n - 1] * hi[n - 1][n];
        }

        if (iter === MLINDEX_BYTES_NUM_10) {
          exshift += x;
          for (let i = low; i <= n; i++) {
            hi[i][i] -= x;
          }
          s = Math.abs(hi[n][n - 1]) + Math.abs(hi[n - 1][n - MLINDEX_BYTES_NUM_2]);
          y = MLINDEX_BYTES_NUM_075 * s;
          x = y;
          w = MLINDEX_BYTES_NUM_04375 * s * s;
        }

        if (iter === MLINDEX_BYTES_NUM_30) {
          s = (y - x) / MLINDEX_BYTES_NUM_2;
          s = s * s + w;
          if (s > 0) {
            s = Math.sqrt(s);
            if (y < x) {
              s = -s;
            }
            s = x - w / ((y - x) / MLINDEX_BYTES_NUM_2 + s);
            for (let i = low; i <= n; i++) {
              hi[i][i] -= s;
            }
            exshift += s;
            w = MLINDEX_BYTES_NUM_0964;
            y = w;
            x = y;
          }
        }

        iter = iter + 1;

        m = n - MLINDEX_BYTES_NUM_2;
        while (m >= l) {
          z = hi[m][m];
          r = x - z;
          s = y - z;
          p = (r * s - w) / hi[m + 1][m] + hi[m][m + 1];
          q = hi[m + 1][m + 1] - z - r - s;
          r = hi[m + MLINDEX_BYTES_NUM_2][m + 1];
          s = Math.abs(p) + Math.abs(q) + Math.abs(r);
          p = p / s;
          q = q / s;
          r = r / s;
          if (m === l) {
            break;
          }
          if (
            Math.abs(hi[m][m - 1]) * (Math.abs(q) + Math.abs(r)) <
            eps * (Math.abs(p) * (Math.abs(hi[m - 1][m - 1]) + Math.abs(z) + Math.abs(hi[m + 1][m + 1])))
          ) {
            break;
          }
          m -= 1;
        }

        for (let i = m + MLINDEX_BYTES_NUM_2; i <= n; i++) {
          hi[i][i - MLINDEX_BYTES_NUM_2] = 0;
          if (i > m + MLINDEX_BYTES_NUM_2) {
            hi[i][i - MLINDEX_BYTES_NUM_3] = 0;
          }
        }
        for (let k = m; k <= n - 1; k++) {
          notlast = k !== n - 1;
          if (k !== m) {
            p = hi[k][k - 1];
            q = hi[k + 1][k - 1];
            r = notlast ? hi[k + MLINDEX_BYTES_NUM_2][k - 1] : 0.0;
            x = Math.abs(p) + Math.abs(q) + Math.abs(r);
            if (x !== 0) {
              p = p / x;
              q = q / x;
              r = r / x;
            }
          }

          if (x === 0) {
            break;
          }

          s = Math.sqrt(p * p + q * q + r * r);
          if (p < 0) {
            s = -s;
          }

          if (s !== 0) {
            if (k !== m) {
              hi[k][k - 1] = -s * x;
            } else if (l !== m) {
              hi[k][k - 1] = -hi[k][k - 1];
            }

            p = p + s;
            x = p / s;
            y = q / s;
            z = r / s;
            q = q / p;
            r = r / p;

            for (let j = k; j < nni; j++) {
              p = hi[k][j] + q * hi[k + 1][j];
              if (notlast) {
                p = p + r * hi[k + MLINDEX_BYTES_NUM_2][j];
                hi[k + MLINDEX_BYTES_NUM_2][j] = hi[k + MLINDEX_BYTES_NUM_2][j] - p * z;
              }

              hi[k][j] = hi[k][j] - p * x;
              hi[k + 1][j] = hi[k + 1][j] - p * y;
            }

            for (let i = 0; i <= Math.min(n, k + MLINDEX_BYTES_NUM_3); i++) {
              p = x * hi[i][k] + y * hi[i][k + 1];
              if (notlast) {
                p = p + z * hi[i][k + MLINDEX_BYTES_NUM_2];
                hi[i][k + MLINDEX_BYTES_NUM_2] = hi[i][k + MLINDEX_BYTES_NUM_2] - p * r;
              }

              hi[i][k] = hi[i][k] - p;
              hi[i][k + 1] = hi[i][k + 1] - p * q;
            }

            for (let i = low; i <= high; i++) {
              p = x * vd[i][k] + y * vd[i][k + 1];
              if (notlast) {
                p = p + z * vd[i][k + MLINDEX_BYTES_NUM_2];
                vd[i][k + MLINDEX_BYTES_NUM_2] = vd[i][k + MLINDEX_BYTES_NUM_2] - p * r;
              }

              vd[i][k] = vd[i][k] - p;
              vd[i][k + 1] = vd[i][k + 1] - p * q;
            }
          }
        }
      }
    }

    if (norm === 0) {
      return;
    }

    for (n = nni - 1; n >= 0; n--) {
      p = di[n];
      q = ei[n];

      if (q === 0) {
        l = n;
        hi[n][n] = 1;
        for (let i = n - 1; i >= 0; i--) {
          w = hi[i][i] - p;
          r = 0.0;
          for (let j = l; j <= n; j++) {
            r = r + hi[i][j] * hi[j][n];
          }

          if (ei[i] < 0) {
            z = w;
            s = r;
          } else {
            l = i;
            if (ei[i] === 0) {
              if (w !== 0) {
                hi[i][n] = -r / w;
              } else {
                hi[i][n] = -r / (eps * norm);
              }
            } else {
              x = hi[i][i + 1];
              y = hi[i + 1][i];
              q = (di[i] - p) * (di[i] - p) + ei[i] * ei[i];
              t = x * s - (z * r) / q;
              hi[i][n] = t;
              if (Math.abs(x) > Math.abs(z)) {
                hi[i + 1][n] = -r - (w * t) / x;
              } else {
                hi[i + 1][n] = -s - (y * t) / z;
              }
            }

            t = Math.abs(hi[i][n]);
            if (eps * t * t > 1) {
              for (let j = i; j <= n; j++) {
                hi[j][n] = hi[j][n] / t;
              }
            }
          }
        }
      } else if (q < 0) {
        l = n - 1;

        if (Math.abs(hi[n][n - 1]) > Math.abs(hi[n - 1][n])) {
          hi[n - 1][n - 1] = q / hi[n][n - 1];
          hi[n - 1][n] = -(hi[n][n] - p) / hi[n][n - 1];
        } else {
          cdivres = this.cdiv(0.0, -Number(hi[n - 1][n]), hi[n - 1][n - 1] - p, q);
          hi[n - 1][n - 1] = cdivres[0];
          hi[n - 1][n] = cdivres[1];
        }

        hi[n][n - 1] = 0;
        hi[n][n] = 1;
        for (let i = n - MLINDEX_BYTES_NUM_2; i >= 0; i--) {
          ra = 0;
          sa = 0;
          for (let j = l; j <= n; j++) {
            ra = ra + hi[i][j] * hi[j][n - 1];
            sa = sa + hi[i][j] * hi[j][n];
          }

          w = hi[i][i] - p;

          if (ei[i] < 0) {
            z = w;
            r = ra;
            s = sa;
          } else {
            l = i;
            if (ei[i] === 0) {
              cdivres = this.cdiv(-ra, -sa, w, q);
              hi[i][n - 1] = cdivres[0];
              hi[i][n] = cdivres[1];
            } else {
              x = hi[i][i + 1];
              y = hi[i + 1][i];
              vr = (di[i] - p) * (di[i] - p) + ei[i] * ei[i] - q * q;
              vi = (di[i] - p) * MLINDEX_BYTES_NUM_2 * q;
              if (vr === 0 && vi === 0) {
                vr = eps * norm * (Math.abs(w) + Math.abs(q) + Math.abs(x) + Math.abs(y) + Math.abs(z));
              }
              cdivres = this.cdiv(x * r - z * ra + q * sa, x * s - z * sa - q * ra, vr, vi);
              hi[i][n - 1] = cdivres[0];
              hi[i][n] = cdivres[1];
              if (Math.abs(x) > Math.abs(z) + Math.abs(q)) {
                hi[i + 1][n - 1] = (-ra - w * hi[i][n - 1] + q * hi[i][n]) / x;
                hi[i + 1][n] = (-sa - w * hi[i][n] - q * hi[i][n - 1]) / x;
              } else {
                cdivres = this.cdiv(-r - y * hi[i][n - 1], -s - y * hi[i][n], z, q);
                hi[i + 1][n - 1] = cdivres[0];
                hi[i + 1][n] = cdivres[1];
              }
            }

            t = Math.max(Math.abs(hi[i][n - 1]), Math.abs(hi[i][n]));
            if (eps * t * t > 1) {
              for (let j = i; j <= n; j++) {
                hi[j][n - 1] = hi[j][n - 1] / t;
                hi[j][n] = hi[j][n] / t;
              }
            }
          }
        }
      }
    }

    for (let i = 0; i < nni; i++) {
      if (i < low || i > high) {
        for (let j = i; j < nni; j++) {
          vd[i][j] = hi[i][j];
        }
      }
    }

    for (let j = nni - 1; j >= low; j--) {
      for (let i = low; i <= high; i++) {
        z = 0;
        for (let k = low; k <= Math.min(j, high); k++) {
          z = z + vd[i][k] * hi[k][j];
        }
        vd[i][j] = z;
      }
    }
  }

  cdiv(xr: number, xi: number, yr: number, yi: number): [number, number] {
    let r: number;
    let d: number;
    if (Math.abs(yr) > Math.abs(yi)) {
      r = yi / yr;
      d = yr + r * yi;
      return [(xr + r * xi) / d, (xi - r * xr) / d];
    } else {
      r = yr / yi;
      d = yi + r * yr;
      return [(r * xr + xi) / d, (r * xi - xr) / d];
    }
  }
}

// ml-matrix src/dc/qr.js
class MlMatrixDcqr {
  qr: Matrix;
  rdiag: number[] = [];

  constructor(qr: Matrix) {
    this.qr = qr;
  }

  qrDecomposition(value: number[][]): void {
    let newValue = Matrix.checkMatrix(value);
    let qr: Matrix = newValue.clone();
    let m: number = newValue.rows;
    let n: number = newValue.columns;
    let rdiag: number[] = Array(n).fill(0);
    let s: number;
    for (let k = 0; k < n; k++) {
      let nrm = 0;
      for (let i = k; i < m; i++) {
        nrm = mlMatrixDcuTil.hypotenuse(nrm, qr.get(i, k));
      }
      if (nrm !== 0) {
        if (qr.get(k, k) < 0) {
          nrm = -nrm;
        }
        for (let i = k; i < m; i++) {
          qr.set(i, k, qr.get(i, k) / nrm);
        }
        qr.set(k, k, qr.get(k, k) + 1);
        for (let j = k + 1; j < n; j++) {
          s = 0.0;
          for (let i = k; i < m; i++) {
            s += qr.get(i, k) * qr.get(i, j);
          }
          s = -s / qr.get(k, k);
          for (let i = k; i < m; i++) {
            qr.set(i, j, qr.get(i, j) + s * qr.get(i, k));
          }
        }
      }
      rdiag[k] = -nrm;
    }
    this.qr = qr;
    this.rdiag = rdiag;
  }

  solve(value: number | number[][] | Matrix): Matrix {
    let newValue: Matrix = Matrix.checkMatrix(value);
    let qr = this.qr;
    let m: number = qr.rows;
    if ((newValue as Matrix).rows !== m) {
      throw new Error('Matrix row dimensions must agree');
    }
    if (!this.isFullRank()) {
      throw new Error('Matrix is rank deficient');
    }
    let count: number = (newValue as Matrix).columns;
    let x: Matrix = (newValue as Matrix).clone();
    let n: number = qr.columns;
    let s: number;
    for (let k = 0; k < n; k++) {
      for (let j = 0; j < count; j++) {
        s = 0;
        for (let i = k; i < m; i++) {
          s += qr.get(i, k) * x.get(i, j);
        }
        s = -s / qr.get(k, k);
        for (let i = k; i < m; i++) {
          x.set(i, j, x.get(i, j) + s * qr.get(i, k));
        }
      }
    }
    for (let k = n - 1; k >= 0; k--) {
      for (let j = 0; j < count; j++) {
        x.set(k, j, x.get(k, j) / (this.rdiag[k] as number));
      }
      for (let i = 0; i < k; i++) {
        for (let j = 0; j < count; j++) {
          x.set(i, j, x.get(k, j) - x.get(k, j) * qr.get(i, k));
        }
      }
    }
    return x.subMatrix(0, n - 1, 0, count - 1);
  }

  isFullRank(): boolean {
    let columns: number = this.qr.columns;
    for (let i = 0; i < columns; i++) {
      if (this.rdiag[i] === 0) {
        return false;
      }
    }
    return true;
  }

  upperTriangularMatrix(): Matrix {
    let qr: Matrix = this.qr;
    let n: number = qr.columns;
    let x: Matrix = new Matrix(n, n);

    for (let i = 0; i < n; i++) {
      for (let j = 0; j < n; j++) {
        if (i < j) {
          x.set(i, j, qr.get(i, j));
        } else if (i === j) {
          x.set(i, j, this.rdiag[i] as number);
        } else {
          x.set(i, j, 0);
        }
      }
    }
    return x;
  }

  orthogonalMatrix(): Matrix {
    let qr: Matrix = this.qr;
    let rows: number = qr.rows;
    let columns: number = qr.columns;
    let x: Matrix = new Matrix(rows, columns);
    let s: number;
    for (let k = columns - 1; k >= 0; k--) {
      for (let i = 0; i < rows; i++) {
        x.set(i, k, 0);
      }
      x.set(k, k, 1.0);
      for (let j = k; j < columns; j++) {
        if (qr.get(k, k) !== 0) {
          s = 0;
          for (let i = k; i < rows; i++) {
            s += qr.get(i, k) * x.get(i, j);
          }

          s = -s / qr.get(k, k);

          for (let i = k; i < rows; i++) {
            x.set(i, j, x.get(i, j) + s * qr.get(i, k));
          }
        }
      }
    }
    return x;
  }
}

// ml-matric src/decompositions.js
class MlMatrixDecompositionsCls {
  inverse(matrix: Matrix): Matrix {
    matrix = Matrix.checkMatrix(matrix);
    return mlMatrixDecompositions.solve(matrix, Matrix.eye(matrix.rows));
  }

  solve(leftHandSide: Matrix, rightHandSide: Matrix): Matrix {
    leftHandSide = Matrix.checkMatrix(leftHandSide);
    rightHandSide = Matrix.checkMatrix(rightHandSide);
    if (leftHandSide.isSquare()) {
      return new MlMatrixDclu(leftHandSide).matrix.solve(rightHandSide);
    } else {
      return new MlMatrixDcqr(leftHandSide).solve(rightHandSide);
    }
  }
}

let mlMatrixDecompositions = new MlMatrixDecompositionsCls();

// feedforward-neural-networks utils.js
class FeedforwardNeuralNetworksUtilsCls {
  sumRow(matrix: Matrix): Matrix {
    let sum: Matrix = Matrix.zeros(matrix.rows, 1);
    for (let i = 0; i < matrix.rows; ++i) {
      for (let j = 0; j < matrix.columns; ++j) {
        sum.set(i, 0, sum.get(i, 0) + matrix.get(i, j));
      }
    }
    return sum;
  }

  sumCol(matrix: Matrix): Matrix {
    let sum: Matrix = Matrix.zeros(1, matrix.columns);
    for (let i = 0; i < matrix.rows; ++i) {
      for (let j = 0; j < matrix.columns; ++j) {
        sum.set(0, j, sum.get(0, j) + matrix.get(i, j));
      }
    }
    return sum;
  }

  dictOutputs(array: number[] | boolean[] | number[][]): NeuralNetworkDict {
    if (Array.isArray(array) && Array.isArray(array[0]) && typeof array[0][0] === 'number') {
      let inputs: Map<String, number> = new Map<String, number>();
      let outputs: Map<number, number[]> = new Map<number, number[]>();
      let index = 0;
      for (let i = 0; i < array.length; i += 1) {
        let key = array[i].toString();
        if (!inputs.has(key)) {
          inputs.set(key, index);
          outputs.set(index, array[i] as number[]);
          index += 1;
        }
      }
      return new NeuralNetworkDict(inputs, outputs);
    } else if (Array.isArray(array) && typeof array[0] === 'number') {
      let inputs: Map<number, number> = new Map<number, number>();
      let outputs: Map<number, number> = new Map<number, number>();
      let index = 0;
      for (let i = 0; i < array.length; i += 1) {
        if (inputs.get(array[i] as number) === undefined) {
          inputs.set(array[i] as number, index);
          outputs.set(index, array[i] as number);
          index += 1;
        }
      }
      return new NeuralNetworkDict(inputs, outputs);
    } else {
      let newArray = array as boolean[];
      let inputs: Map<boolean, number> = new Map<boolean, number>();
      let outputs: Map<number, boolean> = new Map<number, boolean>();
      let index = 0;
      for (let i = 0; i < newArray.length; i += 1) {
        if (inputs.get(newArray[i] as boolean) === undefined) {
          inputs.set(newArray[i] as boolean, index);
          outputs.set(index, newArray[i] as boolean);
          index += 1;
        }
      }
      return new NeuralNetworkDict(inputs, outputs);
    }
  }
}

let feedforwardNeuralNetworksUtils = new FeedforwardNeuralNetworksUtilsCls();
// feedforward-neural-networks activationFunctions.js

interface ActivationFuncation {
  paramsCount: number;
  name: string;

  activation(value: number[]): number;

  derivate(value: number[]): number;
}

class ActivationFuncationTanh implements ActivationFuncation {
  name = 'tanh';
  paramsCount = 1;

  activation(value: number[]): number {
    return Math.tanh(value[0]);
  }

  derivate(value: number[]): number {
    return 1 - value[0] * value[0];
  }
}

class ActivationFuncationIdentity implements ActivationFuncation {
  name = 'identity';
  paramsCount = 1;

  activation(value: number[]): number {
    return value[0];
  }

  derivate(value: number[]): number {
    return 1;
  }
}

class ActivationFuncationLogistic implements ActivationFuncation {
  paramsCount = 1;
  name = 'logistic';

  logistic(val: number): number {
    return 1 / (1 + Math.exp(-val));
  }

  activation(value: number[]): number {
    return this.logistic(value[0]);
  }

  derivate(value: number[]): number {
    return this.logistic(value[0]) * (1 - this.logistic(value[0]));
  }
}

class ActivationFuncationArctan implements ActivationFuncation {
  paramsCount = 1;
  name = 'arctan';

  activation(value: number[]): number {
    return Math.atan(value[0]);
  }

  derivate(value: number[]): number {
    return 1 / (value[0] * value[0] + 1);
  }
}

class ActivationFuncationSoftsign implements ActivationFuncation {
  paramsCount = 1;
  name = 'softsign';

  activation(value: number[]): number {
    return value[0] / (1 + Math.abs(value[0]));
  }

  derivate(value: number[]): number {
    return 1 / ((1 + Math.abs(value[0])) * (1 + Math.abs(value[0])));
  }
}

class ActivationFuncationRelu implements ActivationFuncation {
  paramsCount = 1;
  name = 'relu';

  activation(value: number[]): number {
    return value[0] < 0 ? 0 : value[0];
  }

  derivate(value: number[]): number {
    return value[0] < 0 ? 0 : 1;
  }
}

class ActivationFuncationSoftplus implements ActivationFuncation {
  paramsCount = 1;
  name = 'softplus';

  activation(value: number[]): number {
    return Math.log(1 + Math.exp(value[0]));
  }

  derivate(value: number[]): number {
    return 1 / (1 + Math.exp(-value[0]));
  }
}

class ActivationFuncationBent implements ActivationFuncation {
  paramsCount = 1;
  name = 'bent';

  activation(value: number[]): number {
    return (Math.sqrt(value[0] * value[0] + 1) - 1) / MLINDEX_BYTES_NUM_2 + value[0];
  }

  derivate(value: number[]): number {
    return value[0] / (MLINDEX_BYTES_NUM_2 * Math.sqrt(value[0] * value[0] + 1)) + 1;
  }
}

class ActivationFuncationSinusoid implements ActivationFuncation {
  paramsCount = 1;
  name = 'sinusoid';

  activation(value: number[]): number {
    return Math.sin(value[0]);
  }

  derivate(value: number[]): number {
    return Math.cos(value[0]);
  }
}

class ActivationFuncationSinc implements ActivationFuncation {
  paramsCount = 1;
  name = 'sinc';

  activation(value: number[]): number {
    return value[0] === 0 ? 1 : Math.sin(value[0]) / value[0];
  }

  derivate(val: number[]): number {
    return val[0] === 0 ? 0 : Math.cos(val[0]) / val[0] - Math.sin(val[0]) / (val[0] * val[0]);
  }
}

class ActivationFuncationGaussian implements ActivationFuncation {
  paramsCount = 1;
  name = 'gaussian';

  activation(val: number[]): number {
    return Math.exp(-Math.pow(val[0], val[0]));
  }

  derivate(val: number[]): number {
    return MLINDEX_BYTES_NUM_002 * val[0] * Math.exp(-Math.pow(val[0], val[0]));
  }
}

class ActivationFuncationParametricRelu implements ActivationFuncation {
  paramsCount = MLINDEX_BYTES_NUM_2;
  name = 'parametric-relu';

  activation(value: number[]): number {
    if (value.length > 1) {
      return value[0] < 0 ? value[1] * value[0] : value[0];
    } else {
      return value[0];
    }
  }

  derivate(value: number[]): number {
    if (value.length > 1) {
      return value[0] < 0 ? value[1] : 1;
    } else {
      return value[0];
    }
  }
}

class ActivationFuncationExponentialElu implements ActivationFuncation {
  paramsCount = MLINDEX_BYTES_NUM_2;
  name = 'exponential-elu';

  expElu(val: number, param: number): number {
    return val < 0 ? param * (Math.exp(val) - 1) : val;
  }

  activation(value: number[]): number {
    return this.expElu(value[0], value[1]);
  }

  derivate(value: number[]): number {
    return value[0] < 0 ? this.expElu(value[0], value[1]) + value[1] : 1;
  }
}

class ActivationFuncationSoftExponential implements ActivationFuncation {
  paramsCount = MLINDEX_BYTES_NUM_2;
  name = 'soft-exponential';

  softExponential(val: number, param: number): number {
    if (param < 0) {
      return -Math.log(1 - param * (val + param)) / param;
    } else if (param > 0) {
      return (Math.exp(param * val) - 1) / param + param;
    } else {
      return val;
    }
  }

  softExponentialPrime(val: number, param: number): number {
    if (param < 0) {
      return 1 / (1 - param * (param + val));
    } else {
      return Math.exp(param * val);
    }
  }

  activation(value: number[]): number {
    return this.softExponential(value[0], value[1]);
  }

  derivate(value: number[]): number {
    return this.softExponentialPrime(value[0], value[1]);
  }
}

let ACTIVATION_FUNCTIONS: Array<ActivationFuncation> = [
  new ActivationFuncationTanh(),
  new ActivationFuncationIdentity(),
  new ActivationFuncationLogistic(),
  new ActivationFuncationArctan(),
  new ActivationFuncationSoftsign(),
  new ActivationFuncationRelu(),
  new ActivationFuncationSoftplus(),
  new ActivationFuncationBent(),
  new ActivationFuncationSinusoid(),
  new ActivationFuncationSinc(),
  new ActivationFuncationGaussian(),
  new ActivationFuncationParametricRelu(),
  new ActivationFuncationExponentialElu(),
  new ActivationFuncationSoftExponential()
];

let feedforwardNeuralNetworksActivationFunctions = ACTIVATION_FUNCTIONS;

// feedforward-neural-networks FeedForwardNeuralNetwork.js
class NeuralNetworkDict {
  inputs: Map<number[] | number | boolean | String, number>;
  outputs: Map<number, number[] | number | boolean | String>;

  constructor(inputs: Map<number[] | number | boolean | String, number>, outputs: Map<number, number[] | number | boolean | String>) {
    this.inputs = inputs;
    this.outputs = outputs;
  }
}

class FeedforwardNeuralNetwork {
  hiddenLayers: number[];
  iterations!: number;
  learningRate!: number;
  regularization!: number;
  dicts?: NeuralNetworkDict;
  activation!: string;
  activationParam?: number;
  model: FeedforwardNeuralNetworksLayer[] = [];

  constructor(options: MlOptions) {
    if (options.model !== undefined) {
      this.hiddenLayers = options.hiddenLayers;
      this.iterations = options.iterations;
      this.learningRate = options.learningRate;
      this.regularization = options.regularization;
      this.dicts = options.dicts! as NeuralNetworkDict;
      this.activation = options.activation;
      this.activationParam = options.activationParam;
      this.model = [];
      for (let i = 0; i < options.layers.length - 1; ++i) {
        this.model.push(FeedforwardNeuralNetworksLayer.load(options.layers[i]));
      }
      this.model.push(new FeedforwardNeuralNetworksOutputLayer(options.layers[options.layers.length - 1]));
    } else {
      this.hiddenLayers = options.hiddenLayers.length === 0 ? [MLINDEX_BYTES_NUM_10] : options.hiddenLayers;
      this.iterations = options.iterations === undefined ? MLINDEX_BYTES_NUM_50 : options.iterations;
      this.learningRate = options.learningRate === undefined ? MLINDEX_BYTES_NUM_001 : options.learningRate;
      this.regularization = options.regularization === undefined ? MLINDEX_BYTES_NUM_001 : options.regularization;
      this.activation = options.activation === undefined ? 'tanh' : options.activation;
      this.activationParam = options.activationParam === undefined ? 1 : options.activationParam;
      if (!ACTIVATION_FUNCTIONS.some((fun: ActivationFuncation): boolean => fun.name === this.activation)) {
        this.activation = 'tanh';
      }
    }
  }

  buildNetwork(inputSize: number, outputSize: number): void {
    MLINDEX_BYTES_NUM_2 + (this.hiddenLayers.length - 1);
    this.model = Array<FeedforwardNeuralNetworksLayer>();
    let options: MlOptions = new MlOptions();
    options.inputSize = inputSize;
    options.outputSize = this.hiddenLayers[0];
    options.activation = this.activation;
    options.activationParam = this.activationParam;
    options.regularization = this.regularization;
    options.epsilon = this.learningRate;
    this.model.push(new FeedforwardNeuralNetworksLayer(options));
    for (let i = 1; i < this.hiddenLayers.length; ++i) {
      let option: MlOptions = new MlOptions();
      option.inputSize = this.hiddenLayers[i - 1];
      option.outputSize = this.hiddenLayers[i];
      option.activation = this.activation;
      option.activationParam = this.activationParam!;
      option.regularization = this.regularization;
      option.epsilon = this.learningRate;
      this.model.push(new FeedforwardNeuralNetworksLayer(option));
    }
    let optionOut: MlOptions = new MlOptions();
    optionOut.inputSize = this.hiddenLayers[this.hiddenLayers.length - 1];
    optionOut.outputSize = outputSize;
    optionOut.activation = this.activation;
    optionOut.activationParam = this.activationParam;
    optionOut.regularization = this.regularization;
    optionOut.epsilon = this.learningRate;
    this.model.push(new FeedforwardNeuralNetworksOutputLayer(optionOut));
  }

  train(features: number | number[][] | Matrix, labels: number[] | number[][] | boolean[]): void {
    features = Matrix.checkMatrix(features);
    this.dicts = feedforwardNeuralNetworksUtils.dictOutputs(labels);

    let inputSize: number = features.columns;

    let outputSize = this.dicts!.inputs.size;

    this.buildNetwork(inputSize, outputSize);

    for (let i = 0; i < this.iterations!; ++i) {
      let probabilities = this.propagate(features);
      this.backpropagation(features, labels, probabilities);
    }
  }

  propagate(x: Matrix): Matrix {
    let input = x;
    for (let i = 0; i < this.model.length; ++i) {
      input = this.model[i].forward(input);
    }
    return input.divColumnVector(feedforwardNeuralNetworksUtils.sumRow(input));
  }
  backpropagation(features: Matrix, labels: number[] | number[][] | boolean[], probabilities: Matrix): void {
    let newProbabilities = probabilities;
    for (let i = 0; i < newProbabilities.array!.length; ++i) {
      let key = labels[i];
      if (typeof key === 'boolean') {
        key = key ? 1 : 0;
        newProbabilities.set(i, this.dicts?.inputs.get(key) as number, probabilities.get(i, this.dicts?.inputs.get(key) as number) - 1);
      } else {
        if (Array.isArray(labels[i]) && typeof labels[0] === 'number') {
          let key = labels[i].toString();
          newProbabilities.set(i, this.dicts?.inputs.get(key) as number, probabilities.get(i, this.dicts?.inputs.get(key) as number) - 1);
        } else {
          newProbabilities.set(i, this.dicts?.inputs.get(labels[i]) as number, probabilities.get(i, this.dicts?.inputs.get(labels[i]) as number) - 1);
        }
      }
    }
    let delta = newProbabilities;
    for (let i = this.model.length - 1; i >= 0; --i) {
      let a: Matrix = i > 0 ? this.model[i - 1].a : features;
      delta = this.model[i].backpropagation(delta, a);
    }

    for (let i = 0; i < this.model.length; ++i) {
      this.model[i].update();
    }
  }

  predict(features: number[][] | Matrix): number[] | boolean[] | number[][] {
    features = Matrix.checkMatrix(features);
    let outputs = new Array<number | boolean | number[]>();
    let probabilities = this.propagate(features);
    for (let i = 0; i < features.rows; i++) {
      let result = this.dicts?.outputs.get(probabilities.maxRowIndex(i)[1]);
      outputs.push(result as number | boolean | number[]);
    }
    return outputs as number[] | boolean[] | number[][];
  }

  toJSON(): MlOptions {
    let model: MlOptions = new MlOptions();
    model.model = 'FNN';
    model.hiddenLayers = this.hiddenLayers;
    model.iterations = this.iterations;
    model.learningRate = this.learningRate;
    model.regularization = this.regularization;
    model.activation = this.activation;
    model.activationParam = this.activationParam;
    model.dicts = this.dicts;
    model.layers = [];
    for (let i = 0; i < this.model.length; i++) {
      model.layers.push(this.model[i].toJSON());
    }
    return model;
  }

  static load(model: MlOptions): FeedforwardNeuralNetwork {
    if (model.model !== 'FNN') {
      throw new RangeError('the current model is not a feed forward network');
    }
    return new FeedforwardNeuralNetwork(model);
  }
}

// feedforward-neural-networks Layer.js

class FeedforwardNeuralNetworksLayer {
  inputSize?: number;
  outputSize?: number;
  regularization!: number;
  epsilon?: number;
  activation!: string;
  activationParam?: number;
  w!: Matrix;
  b!: Matrix;
  a!: Matrix;
  dw!: Matrix;
  db!: Matrix;
  activationFunction?: (matrix: Matrix, row: number, column: number) => void;
  derivate?: (matrix: Matrix, row: number, column: number) => void;

  constructor(options: MlOptions) {
    this.inputSize = options.inputSize;
    this.outputSize = options.outputSize;
    this.regularization = options.regularization;
    this.epsilon = options.epsilon;
    this.activation = options.activation;
    this.activationParam = options.activationParam;

    let selectedFunction: ActivationFuncation = ACTIVATION_FUNCTIONS.filter((e: ActivationFuncation) => e.name === this.activation)[0];

    let actFunction: (n: number) => number;
    if (selectedFunction.paramsCount === MLINDEX_BYTES_NUM_2) {
      actFunction = (val: number): number => {
        if (this.activationParam !== undefined) {
          return selectedFunction.activation([val, this.activationParam!]);
        } else {
          return selectedFunction.activation([val, 1]);
        }
      };
    } else {
      actFunction = (val: number): number => {
        return selectedFunction.activation([val]);
      };
    }
    this.activationFunction = (matrix: Matrix, i: number, j: number): void => {
      matrix.set(i, j, actFunction(matrix.get(i, j)));
    };
    let derFunction: (n: number) => number;
    if (selectedFunction.paramsCount === MLINDEX_BYTES_NUM_2) {
      derFunction = (val: number): number => {
        return selectedFunction.derivate([val, this.activationParam!]);
      };
    } else {
      derFunction = (val: number): number => {
        return selectedFunction.derivate([val]);
      };
    }
    this.derivate = (matrix: Matrix, i: number, j: number): void => {
      matrix.set(i, j, derFunction(matrix.get(i, j)));
    };

    if (options.model !== undefined) {
      this.w = Matrix.checkMatrix(options.w);
      this.b = Matrix.checkMatrix(options.b);
    } else {
      this.w = Matrix.rand(this.inputSize!, this.outputSize!);
      this.b = Matrix.zeros(1, this.outputSize!);
      this.w.apply((matrix: Matrix, i: number, j: number) => {
        matrix.set(i, j, matrix.get(i, j) / Math.sqrt(Number(options.inputSize!)));
      });
    }
  }

  forward(x: Matrix): Matrix {
    let z: Matrix = x.mmul(this.w).addRowVector(this.b);
    z.apply(this.activationFunction!);
    this.a = z.clone();
    return z;
  }

  backpropagation(delta: Matrix, a: Matrix): Matrix {
    this.dw = a.transposeView().mmul(delta);
    this.db = feedforwardNeuralNetworksUtils.sumCol(delta);
    let aCopy: Matrix = a.clone();
    return delta.mmul(this.w!.transposeView()).mul(aCopy.apply(this.derivate!));
  }

  update(): void {
    this.dw.add(this.w.clone().mul(this.regularization!));
    this.w.add(this.dw.mul(-this.epsilon!));
    this.b.add(this.db.mul(-this.epsilon!));
  }

  toJSON(): MlOptions {
    let model: MlOptions = new MlOptions();
    model.model = 'Layer';
    model.inputSize = this.inputSize;
    model.outputSize = this.outputSize;
    model.regularization = this.regularization;
    model.epsilon = this.epsilon;
    model.activation = this.activation;
    model.w = this.w;
    model.b = this.b;
    return model;
  }

  static load(model: MlOptions): FeedforwardNeuralNetworksLayer {
    if (model.model !== 'Layer') {
      throw new RangeError('the current model is not a Layer model');
    }
    return new FeedforwardNeuralNetworksLayer(model);
  }
}

// feedforward-neural-networks OutputLayer.js
class FeedforwardNeuralNetworksOutputLayer extends FeedforwardNeuralNetworksLayer {
  constructor(options: MlOptions) {
    super(options);
    this.activationFunction = (matrix: Matrix, i: number, j: number): void => {
      matrix.set(i, j, Math.exp(matrix.get(i, j)));
    };
  }

  static load1(model: MlOptions): FeedforwardNeuralNetworksOutputLayer {
    if (model.model !== 'Layer') {
      throw new RangeError('the current model is not a Layer model');
    }
    return new FeedforwardNeuralNetworksOutputLayer(model);
  }
}

/*
 * @State
 * @Tags Jetstream2
 */
export class Benchmark {
  it(name: string, f: () => void): void {
    f();
  }
  run(): void {
    let result: number[] | boolean[] | number[][];
    let functions: ActivationFuncation[] = ACTIVATION_FUNCTIONS;
    this.it('Training the neural network with XOR operator', () => {
      {
        let source = [
          [0.0, 0.0],
          [0.0, 1.0],
          [1.0, 0.0],
          [1.0, 1.0]
        ];
        let trainingSet: Matrix = new Matrix(source);
        let predictions = [0, 1, 1, 0];
        for (let i = 0; i < functions.length; i++) {
          let options: MlOptions = new MlOptions();
          options.hiddenLayers = [MLINDEX_BYTES_NUM_4];
          options.iterations = MLINDEX_BYTES_NUM_040;
          options.learningRate = MLINDEX_BYTES_NUM_03;
          options.activation = functions[i].name;
          let xorNn: FeedforwardNeuralNetwork = new FeedforwardNeuralNetwork(options);
          xorNn.train(trainingSet, predictions);
          result = xorNn.predict(trainingSet);
        }
      }
    });
    this.it('Training the neural network with AND operator', () => {
      let trainingSet: number[][] = [
        [0, 0],
        [0, 1],
        [1, 0],
        [1, 1]
      ];
      let predictions = [
        [1, 0],
        [1, 0],
        [1, 0],
        [0, 1]
      ];
      for (let i = 0; i < functions.length; i++) {
        let options: MlOptions = new MlOptions();
        options.hiddenLayers = [MLINDEX_BYTES_NUM_3];
        options.iterations = MLINDEX_BYTES_NUM_75;
        options.learningRate = MLINDEX_BYTES_NUM_03;
        options.activation = functions[i].name;
        let andNn: FeedforwardNeuralNetwork = new FeedforwardNeuralNetwork(options);
        andNn.train(trainingSet, predictions);
        result = andNn.predict(trainingSet);
      }
    });
    this.it('Export and import', () => {
      let trainingSet: number[][] = [
        [0, 0],
        [0, 1],
        [1, 0],
        [1, 1]
      ];
      let predictions = [0, 1, 1, 1];
      for (let i = 0; i < functions.length; i++) {
        let options: MlOptions = new MlOptions();
        options.hiddenLayers = [MLINDEX_BYTES_NUM_4];
        options.iterations = MLINDEX_BYTES_NUM_040;
        options.learningRate = MLINDEX_BYTES_NUM_03;
        options.activation = functions[i].name;
        let nn: FeedforwardNeuralNetwork = new FeedforwardNeuralNetwork(options);
        nn.train(trainingSet, predictions);
        let model: MlOptions = nn.toJSON();
        let networkNn: FeedforwardNeuralNetwork = FeedforwardNeuralNetwork.load(model);
        result = networkNn.predict(trainingSet);
      }
    });
    this.it('Multiclass clasification', () => {
      let trainingSet: number[][] = [
        [0, 0],
        [0, 1],
        [1, 0],
        [1, 1]
      ];
      let predictions = [MLINDEX_BYTES_NUM_2, 0, 1, 0];
      for (let i = 0; i < functions.length; i++) {
        let options: MlOptions = new MlOptions();
        options.hiddenLayers = [MLINDEX_BYTES_NUM_4];
        options.iterations = MLINDEX_BYTES_NUM_040;
        options.learningRate = MLINDEX_BYTES_NUM_05;
        options.activation = functions[i].name;
        let nn: FeedforwardNeuralNetwork = new FeedforwardNeuralNetwork(options);
        nn.train(trainingSet, predictions);
        result = nn.predict(trainingSet);
      }
    });
    this.it('Big case', () => {
      let trainingSet: number[][] = [
        [1, 1],
        [1, MLINDEX_BYTES_NUM_2],
        [MLINDEX_BYTES_NUM_2, 1],
        [MLINDEX_BYTES_NUM_2, MLINDEX_BYTES_NUM_2],
        [MLINDEX_BYTES_NUM_3, 1],
        [1, MLINDEX_BYTES_NUM_3],
        [1, MLINDEX_BYTES_NUM_4],
        [MLINDEX_BYTES_NUM_4, 1],
        [MLINDEX_BYTES_NUM_6, 1],
        [MLINDEX_BYTES_NUM_6, MLINDEX_BYTES_NUM_2],
        [MLINDEX_BYTES_NUM_6, MLINDEX_BYTES_NUM_3],
        [MLINDEX_BYTES_NUM_6, MLINDEX_BYTES_NUM_4],
        [MLINDEX_BYTES_NUM_6, MLINDEX_BYTES_NUM_5],
        [MLINDEX_BYTES_NUM_5, MLINDEX_BYTES_NUM_5],
        [MLINDEX_BYTES_NUM_4, MLINDEX_BYTES_NUM_5],
        [MLINDEX_BYTES_NUM_3, MLINDEX_BYTES_NUM_5]
      ];
      let predictions = [
        [1, 0],
        [1, 0],
        [1, 0],
        [1, 0],
        [1, 0],
        [1, 0],
        [1, 0],
        [1, 0],
        [0, 1],
        [0, 1],
        [0, 1],
        [0, 1],
        [0, 1],
        [0, 1],
        [0, 1],
        [0, 1]
      ];
      for (let i = 0; i < functions.length; i++) {
        let options: MlOptions = new MlOptions();
        options.hiddenLayers = [MLINDEX_BYTES_NUM_020];
        options.iterations = NUM_TIME_LOOP_CONST;
        options.learningRate = MLINDEX_BYTES_NUM_001;
        options.activation = functions[i].name;
        let nn: FeedforwardNeuralNetwork = new FeedforwardNeuralNetwork(options);
        nn.train(trainingSet, predictions);
        result = nn.predict([[MLINDEX_BYTES_NUM_050, MLINDEX_BYTES_NUM_40]]);
      }
    });
  }

  /*
   * @Beanchmark
   */
  runIteration(): void {
    let start = ArkTools.timeInUs() / NUM_STIME_CONST;
    for (let i = 0; i < NUM_TIME_LOOP_5; i++) {
      this.run();
    }
    let end = ArkTools.timeInUs() / NUM_STIME_CONST;
    print('ml: ms = ' + (end - start));
  }
}

declare interface ArkTools {
  timeInUs(args: number): number;
}

let loopCountForPreheat = 1;
for (let i = 0; i < loopCountForPreheat; i++) {
  new Benchmark().runIteration();
}

ArkTools.waitAllJitCompileFinish();

new Benchmark().runIteration();
